diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..79f287a87 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributing + +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). + +This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this. + +**NOTE**: GitHub no longer accepts new services. If you'd like to integrate +your application or service with GitHub, you should use [webhooks][webhooks] which will `POST` a payload to your server for each event or request to join the [GitHub Marketplace][github-marketplace]. + +## Updating an existing service + +GitHub will only accept pull requests to existing services that implement bug fixes or security improvements. We no longer accept feature changes to existing services. + +All pull requests will be reviewed by multiple GitHub staff members before being merged. To allow ample time for review, testing and deployment, there may be a substantial delay between the pull request being created and the pull request being merged by GitHub's staff. + +[code-of-conduct]: http://todogroup.org/opencodeofconduct/#GitHub%20Services/opensource@github.com +[webhooks]: https://developer.github.com/webhooks/ +[github-marketplace]: https://github.com/marketplace diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..c3187f4db --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1 @@ +Please contact GitHub support instead of creating an issue: https://github.com/contact diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..9960d253b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1 @@ +Please note: GitHub will only accept pull requests to existing services that implement bug fixes or security improvements. We no longer accept feature changes to existing services. diff --git a/.gitignore b/.gitignore index 2935ee499..cd770bf1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,22 @@ .DS_Store +.idea deploy.rb *~ +*.sw[po] config/secrets.yml config/services.json /bin vendor/gems .bundle docs/payload_data -.rvmrc \ No newline at end of file +.rvmrc +.project +.settings +.buildpath +log +tmp +pkg +.iml +Gemfile.lock + +github-services*.gem diff --git a/.rbenv-version b/.rbenv-version deleted file mode 100644 index b7bc38ade..000000000 --- a/.rbenv-version +++ /dev/null @@ -1 +0,0 @@ -1.8.7-p352 diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..197c4d5c2 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.4.0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..4d14cdc45 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: ruby +rvm: + - 2.4.0 +install: script/bootstrap +script: bundle exec rake test +sudo: false diff --git a/Gemfile b/Gemfile index c99c1c439..d6e2e3d0d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,22 +1,6 @@ -source "http://rubygems.org" +source "https://rubygems.org" -gem "activeresource", "~> 3.0.0" -gem "amqp", "0.6.7", :require => 'mq' -gem "httparty", "0.7.4" -gem 'yajl-ruby', '1.1.0', :require => 'yajl/json_gem' -gem "mash", "~> 0.1.1" -gem "mime-types", "~> 1.15", :require => 'mime/types' -gem "oauth", "0.4.4" -gem "sinatra", "~> 1.2.6" -gem "tilt", "~> 1.2.1" -gem "tinder", "1.7.0" -gem "mail", "~>2.3" -gem "xml-simple", "1.0.11", :require => 'xmlsimple' -gem "xmpp4r-simple", "0.8.8" -#gem "yammer4r", "0.1.5" -gem "ruby-hmac", "0.4.0" -gem "thin", "1.2.2" -gem "faraday", "0.7.5.pre" -gem "rake", "0.8.7" -gem "statsd-ruby", "0.3.0.github.1" -gem "twilio-ruby", "3.4.2" +gemspec + +gem "rake", "10.0.3" +gem "minitest", :require => nil diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index bde05dfa3..000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,105 +0,0 @@ -GEM - remote: http://rubygems.org/ - specs: - activemodel (3.0.10) - activesupport (= 3.0.10) - builder (~> 2.1.2) - i18n (~> 0.5.0) - activeresource (3.0.10) - activemodel (= 3.0.10) - activesupport (= 3.0.10) - activesupport (3.0.10) - addressable (2.2.6) - amqp (0.6.7) - eventmachine (>= 0.12.4) - builder (2.1.2) - crack (0.1.8) - daemons (1.1.0) - eventmachine (0.12.10) - faraday (0.7.5.pre) - addressable (~> 2.2.6) - multipart-post (~> 1.1.0) - rack (>= 1.1.0, < 2) - faraday_middleware (0.7.0) - faraday (~> 0.7.3) - hashie (1.1.0) - http_parser.rb (0.5.3) - httparty (0.7.4) - crack (= 0.1.8) - i18n (0.5.0) - json (1.6.1) - jwt (0.1.3) - json (>= 1.2.4) - mail (2.3.0) - i18n (>= 0.4.0) - mime-types (~> 1.16) - treetop (~> 1.4.8) - mash (0.1.1) - mime-types (1.16) - multi_json (1.0.3) - multipart-post (1.1.3) - oauth (0.4.4) - polyglot (0.3.3) - rack (1.3.0) - rake (0.8.7) - ruby-hmac (0.4.0) - simple_oauth (0.1.5) - sinatra (1.2.6) - rack (~> 1.1) - tilt (>= 1.2.2, < 2.0) - statsd-ruby (0.3.0.github.1) - thin (1.2.2) - daemons (>= 1.0.9) - eventmachine (>= 0.12.6) - rack (>= 1.0.0) - tilt (1.2.2) - tinder (1.7.0) - activesupport (>= 2.3, < 4) - eventmachine (~> 0.12) - faraday (>= 0.6, < 0.8) - faraday_middleware (>= 0.6, < 0.8) - hashie (~> 1.0) - mime-types (~> 1.16) - multi_json (~> 1.0) - multipart-post (~> 1.1) - twitter-stream (~> 0.1) - treetop (1.4.10) - polyglot - polyglot (>= 0.3.1) - twilio-ruby (3.4.2) - builder (>= 2.1.2) - jwt (>= 0.1.2) - multi_json (>= 1.0.3) - twitter-stream (0.1.14) - eventmachine (>= 0.12.8) - http_parser.rb (~> 0.5.1) - simple_oauth (~> 0.1.4) - xml-simple (1.0.11) - xmpp4r (0.5) - xmpp4r-simple (0.8.8) - xmpp4r (>= 0.3.2) - yajl-ruby (1.1.0) - -PLATFORMS - ruby - -DEPENDENCIES - activeresource (~> 3.0.0) - amqp (= 0.6.7) - faraday (= 0.7.5.pre) - httparty (= 0.7.4) - mail (~> 2.3) - mash (~> 0.1.1) - mime-types (~> 1.15) - oauth (= 0.4.4) - rake (= 0.8.7) - ruby-hmac (= 0.4.0) - sinatra (~> 1.2.6) - statsd-ruby (= 0.3.0.github.1) - thin (= 1.2.2) - tilt (~> 1.2.1) - tinder (= 1.7.0) - twilio-ruby (= 3.4.2) - xml-simple (= 1.0.11) - xmpp4r-simple (= 0.8.8) - yajl-ruby (= 1.1.0) diff --git a/README.md b/README.md new file mode 100644 index 000000000..e6e708a00 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ + +🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 + +**_GitHub Services has been deprecated_. No more contributions will be accepted. Please see our [blog post](https://developer.github.com/changes/2018-04-25-github-services-deprecation/) for more information.** + +🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 + +GitHub Services +=============== + +This repository contains code to integrate GitHub.com with third party services. + +See the [Contributing Guidelines](https://github.com/github/github-services/blob/master/.github/CONTRIBUTING.md) for instructions on contributing a service. + +Current Status +============== + +Please note: GitHub will only accept pull requests to existing services that implement bug fixes or security improvements. We no longer accept feature changes to existing services. + +[![Build Status](https://travis-ci.org/github/github-services.svg?branch=master)](https://travis-ci.org/github/github-services) diff --git a/README.mkdn b/README.mkdn deleted file mode 100644 index 18a567a37..000000000 --- a/README.mkdn +++ /dev/null @@ -1,91 +0,0 @@ -GitHub Services -=============== - -How the services work ---------------------- - -1. A post-receive background job is submitted when someone pushes their - commits to GitHub -2. If the repository the commits belong to has any "Service Hooks" setup, the - job makes a request to `http://services-server/service_name/push` with the - following data: - - `params[:payload]` containing all of the commit data (the same data you get using the API) - - `params[:data]` containing the service data (username, password, room, etc) -3. Sinatra (github-services.rb) processes the request (twitters your data, says - something in campfire, posts it to lighthouse, etc) -4. Rinse and repeat - -Steps to contributing ---------------------- - -1. Fork the project -2. Create a new file in /services/ called `service_name.rb`, using the [following - template](https://github.com/github/github-services/tree/master/services#readme): - - ```ruby - class Service::ServiceName < Service - def receive_push - end - end - ``` - -3. Vendor any external gems your code relies on, and make sure it is - specified in the Gemfile. -4. Add documentation to `docs/service_name` (refer to the others for guidance) -5. Send a pull request from your fork to [github/github-services](https://github.com/github/github-services) -6. Once it's accepted we'll add any new necessary data fields to the GitHub - front-end so people can start using your addition. - -*Patches including tests are encouraged* - -A huge thanks goes out to [our many contributors](https://github.com/github/github-services/contributors)! - -Running the server locally --------------------------- - -1. [sudo] gem install hpricot -2. git clone git://github.com/github/github-services.git -3. cd github-services -4. ruby github-services.rb - -* Bugs in the code should be filed under the Issues tab -* Problems with the service hooks can be filed - [here](https://github.com/contact) - -How to test your service ------------------------- - -You can test your service in a ruby irb console: - -1. Run `rake console` to start irb. -2. Instantiate your Service: - - ```ruby - svc = Service::MyService.new(:push, - # Hash of configuration information. - {'token' => 'abc'}, - # Hash of payload. - {'blah' => 'payload!'}) - - svc.receive_push - ``` - -3. The third argument is optional if you just want to use the sample - payload. - - ```ruby - svc = Service::MyService.new(:push, - # Hash of configuration information. - {'token' => 'abc'}) - - svc.receive_push - ``` - -You can also test your hook with the Sinatra web service: - -1. Start the github-services Sinatra server with `ruby github-services.rb`. By - default, it runs on port 8080. -2. Edit the docs/github_payload file as necessary to test your service. (Usually - just editing the "data" values but leaving the "payload" alone.) -3. Send the docs/github_payload file to your service by calling: - `./script/deliver_payload [service-name]` diff --git a/Rakefile b/Rakefile index 553773e31..fb95d85ea 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +require 'rubygems' +require 'bundler/setup' require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' @@ -5,25 +7,62 @@ Rake::TestTask.new(:test) do |test| test.verbose = true end -task :default => :test +namespace :services do + task :load do + require File.expand_path("../config/load", __FILE__) + end -task :console do - sh "irb -r ./config/load" -end + desc "Writes JSON config to FILE || config/services.json, Docs to DOCS" + task :build => [:config, :docs] -namespace :services do desc "Writes a JSON config to FILE || config/services.json" - task :config do - file = ENV["FILE"] || File.expand_path("../config/services.json", __FILE__) - require File.expand_path("../config/load", __FILE__) + task :config => :load do + sha = `git rev-parse HEAD`.chomp + file = ENV["FILE"] || default_services_config services = [] + Service.load_services Service.services.each do |svc| - services << {:name => svc.hook_name, :events => svc.default_events, + services << {:name => svc.hook_name, :events => svc.default_events, :supported_events => svc.supported_events, :title => svc.title, :schema => svc.schema} end services.sort! { |x, y| x[:name] <=> y[:name] } + data = { + :metadata => { :generated_at => Time.now.utc, :sha => sha }, + :services => services + } + puts "Writing config to #{file}" File.open file, 'w' do |io| - io << Yajl.dump(services, :pretty => true) + io << Yajl.dump(data, :pretty => true) + end + end + + desc "Writes Docs to DOCS" + task :docs => :load do + dir = ENV['DOCS'] || default_docs_dir + docs = Dir[File.expand_path("../docs/*", __FILE__)] + docs.each do |path| + name = File.basename(path) + next if GitHubDocs.include?(name) + new_name = dir.include?('{name}') ? dir.sub('{name}', name) : File.join(dir, name) + new_dir = File.dirname(new_name) + FileUtils.mkdir_p(new_dir) + puts "COPY #{path} => #{new_name}" + FileUtils.cp(path, new_name) end end + + require 'set' + GitHubDocs = Set.new(%w(github_payload payload_data)) + + def base_github_path + ENV['GH_PATH'] || "#{ENV['HOME']}/github/github" + end + + def default_services_config + "#{base_github_path}/config/services.json" + end + + def default_docs_dir + "#{base_github_path}/app/views/edit_repositories/hooks/_{name}.erb" + end end diff --git a/config.ru b/config.ru deleted file mode 100644 index 968d0bcd0..000000000 --- a/config.ru +++ /dev/null @@ -1,7 +0,0 @@ -require File.expand_path('../config/load', __FILE__) - -Service::App.set :environment => :production, - :port => ARGV.first || 8080, - :logging => true - -run Service::App diff --git a/config/console.rb b/config/console.rb new file mode 100644 index 000000000..bc83fb2c5 --- /dev/null +++ b/config/console.rb @@ -0,0 +1,2 @@ +require File.expand_path("../load", __FILE__) +Service.load_services diff --git a/config/load.rb b/config/load.rb index 489b2e7a3..48a00a5e1 100644 --- a/config/load.rb +++ b/config/load.rb @@ -1,114 +1,5 @@ -is_production = ENV['RACK_ENV'] == 'production' || !!ENV['GEM_STRICT'] -services_root = File.expand_path('../../', __FILE__) - -if is_production - # Verify the environment has been bootstrapped by checking that the - # .bundle/loadpath file exists. - if !File.exist?("#{services_root}/.bundle/loadpath") - warn "WARN The gem environment is out-of-date or has yet to be bootstrapped." - warn " Run script/bootstrap to remedy this situation." - fail "gem environment not configued" - end -else - # Run a more exhaustive bootstrap check in non-production environments by making - # sure the Gemfile matches the .bundle/loadpath file checksum. - # - # Verify the environment has been bootstrapped by checking that the - # .bundle/loadpath file exists. - if !File.exist?("#{services_root}/.bundle/loadpath") - warn "WARN The gem environment is out-of-date or has yet to be bootstrapped." - warn " Runnning script/bootstrap to remedy this situation..." - system "#{services_root}/script/bootstrap --local" - - if !File.exist?("#{services_root}/.bundle/loadpath") - warn "WARN The gem environment is STILL out-of-date." - warn " Please contact your network administrator." - fail "gem environment not configued" - end - end - - checksum = File.read("#{services_root}/.bundle/checksum").to_i rescue nil - if `cksum <'#{services_root}/Gemfile'`.to_i != checksum - warn "WARN The gem environment is out-of-date or has yet to be bootstrapped." - warn " Runnning script/bootstrap to remedy this situation..." - system "#{services_root}/script/bootstrap --local" - - checksum = File.read("#{services_root}/.bundle/checksum").to_i rescue nil - if `cksum <'#{services_root}/Gemfile'`.to_i != checksum - warn "WARN The gem environment is STILL out-of-date." - warn " Please contact your network administrator." - fail "gem environment not configued" - end - end -end - -# Disallow use of system gems by default in staging and production environments -# or when the GEM_STRICT environment variable is set. This ensures the gem -# environment is totally isolated to only stuff specified in the Gemfile. -if is_production - ENV['GEM_PATH'] = "#{services_root}/vendor/gems" - ENV['GEM_HOME'] = "#{services_root}/vendor/gems" -elsif !ENV['GEM_PATH'].to_s.include?("#{services_root}/vendor/gems") - ENV['GEM_PATH'] = - ["#{services_root}/vendor/gems", ENV['GEM_PATH']].compact.join(':') -end - -# put RAILS_ROOT/bin on PATH -binpath = "#{services_root}/bin" -ENV['PATH'] = "#{binpath}:#{ENV['PATH']}" if !ENV['PATH'].include?(binpath) - -# Setup bundled gem load path. -paths = File.read("#{services_root}/.bundle/loadpath").split("\n") -paths.each do |path| - next if path =~ /^[ \t]*(?:#|$)/ - path = File.join(services_root, path) - $: << path if !$:.include?(path) -end - -# Add RAILS_ROOT to load path so you can require config/initializers/file -# and stuff like that. -$:.unshift services_root if !$:.include?(services_root) - +require 'rubygems' +require 'bundler/setup' $:.unshift *Dir["#{File.dirname(__FILE__)}/../vendor/internal-gems/**/lib"] -# Child processes inherit our load path. -ENV['RUBYLIB'] = $:.compact.join(':') - -# stdlib -require 'net/http' -require 'net/https' -require 'net/smtp' -require 'socket' -require 'xmlrpc/client' -require 'openssl' -require 'cgi' -#~ require 'date' # This is needed by the CIA service in ruby 1.8.7 or later - -# bundled -require 'mime/types' -require 'xmlsimple' -require 'active_resource' -require 'rack' -require 'sinatra/base' -require 'tinder' -require 'yajl/json_gem' -require 'basecamp' -require 'mail' -require 'xmpp4r' -require 'xmpp4r/jid.rb' -require 'xmpp4r/presence.rb' -require 'xmpp4r/muc.rb' -require 'xmpp4r-simple' -require 'rubyforge' -require 'oauth' -require 'yammer4r' -require 'mq' -require 'statsd' -require 'twilio-ruby' - -# vendor -require 'basecamp' -require 'rubyforge' - -require File.expand_path('../../lib/service', __FILE__) -require File.expand_path('../../lib/app', __FILE__) +require File.expand_path("../../lib/github-services", __FILE__) diff --git a/docs/active_collab b/docs/active_collab deleted file mode 100644 index ec5319d78..000000000 --- a/docs/active_collab +++ /dev/null @@ -1,22 +0,0 @@ -ActiveCollab -======== - -Install Notes -------------- - -1. Currently, you must specify in the configuration milestone and or category if you want the commits to be associated with them. - In the future, I plan for this to have some form of configuration via tags / branches, or something like that. -2. URL, Project ID, and Token are required the others not so much. - -Developer Notes ---------------- - -data - - url (called URL in /activecollab/public/index.php?path_info=people/:company_id/users/:user_id/api) - - token (called Key in /activecollab/public/index.php?path_info=people/:company_id/users/:user_id/api) - - project_id (in URL for /activecollab/public/index.php?path_info=projects/:project_id) - - milestone_id (*OPTIONAL* in URL for /activecollab/public/index.php?path_info=projects/:project_id/milestones/:milestone_id) - - category_id (*OPTIONAL* in URL for /activecollab/public/index.php?path_info=projects/:project_id/discussions&category_id=:category_id) - -payload - - refer to docs/github_payload diff --git a/docs/activecollab b/docs/activecollab new file mode 100644 index 000000000..fc454bffc --- /dev/null +++ b/docs/activecollab @@ -0,0 +1,6 @@ +Install Notes +------------- + +1. Currently, you must specify in the configuration milestone and or category if you want the commits to be associated with them. + In the future, I plan for this to have some form of configuration via tags / branches, or something like that. +2. URL, Project ID, and Token are required the others not so much. diff --git a/docs/acunote b/docs/acunote index c72376873..9262a376a 100644 --- a/docs/acunote +++ b/docs/acunote @@ -1,20 +1,11 @@ -Acunote -======= - -Acunote is an online project management and Scrum software. Integrating it with GitHub lets you see commits in the Timeline, perform code review on commits and create code inspection tasks. See http://www.acunote.com/ for more information. - +Acunote is an online project management and Scrum software. Integrating it with +GitHub lets you see commits in the Timeline, perform code review on commits and +create code inspection tasks. See http://www.acunote.com/ for more information. Install Notes ------------- - 1. Token - GitHub Service Hook Token for your Acunote organization. Get it from "Edit Organization" > "Repositories" page in Acunote. - - -Developer Notes ---------------- - -data - - token +1. Token - GitHub Service Hook Token for your Acunote organization. Get it + from "Edit Organization" > "Repositories" page in Acunote. +2. Don't forget to check "Active" -payload - - refer to docs/github_payload diff --git a/docs/agilezen b/docs/agilezen deleted file mode 100644 index 8dc6f3d0a..000000000 --- a/docs/agilezen +++ /dev/null @@ -1,20 +0,0 @@ -AgileZen -======== - -AgileZen is a simple and easy to use project management tool that helps you collaborate with your team and visualize all your work in progress. - -Integrating with GitHub will allow you to associate Git commits to specific story cards. If you add a story number to your commit message, AgileZen will associate the commit with the story, making it visible on the board and story focus screen. For example, if you add the text `#123` to your commit message, AgileZen will attach the commit to story card 123. - -This allows your team to stay up to date on the latest development work and view a history of commits for each story. - -Install Notes -------------- - -1. **API Key** - This is your AgileZen API key. -2. **Project ID** - This is the project's numeric ID (the number in the project's URL on AgileZen) to associate with the repository. -3. **Branches** - This is a space-separated list of branches to watch commits for. Commits to other branches will not be notified to AgileZen. - -Need Help? ----------- - -Check out our [step by step instructions](http://help.agilezen.com/kb/integrations/github). \ No newline at end of file diff --git a/docs/amazonsns b/docs/amazonsns new file mode 100644 index 000000000..78517c128 --- /dev/null +++ b/docs/amazonsns @@ -0,0 +1,34 @@ +This service lets you publish event messages to Amazon's Simple Notification Service. Please note that SNS Topics are region specific. + +The AWS Key and Secret you provide can either be from your master account (not recommended) or from an IAM Resource. + +If using IAM, be sure to check that the user has the correct policies for publishing to the specified SNS Topic. A policy similar to the one below will provide your IAM Resource with the correct permission level. + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sns:Publish" + ], + "Sid": "Stmt0000000000000", + "Resource": [ + "arn:aws:sns:us-east-1:718656560584:app-deploy" + ], + "Effect": "Allow" + } + ] +} +``` + +This service will attempt to provide you with meaningful errors if your configuration is incorrect. + +1. 'aws_key' (Required) The access key to an Amazon Account or IAM User. + +2. 'aws_secret' (Required) The Amazon secret access key associated with the AWS Key. + +3. 'sns_topic' (Required) Full ARN path to the SNS Topic, ie. `arn:aws:sns:eu-west-1:718656560584:sns_topic_name` + +4. 'sns_region' (Optional) the identifier for the AWS Region that the SNS Topic is located in. This defaults to `us-east-1`. + diff --git a/docs/amqp b/docs/amqp deleted file mode 100644 index 5d1906ed4..000000000 --- a/docs/amqp +++ /dev/null @@ -1,48 +0,0 @@ -AMQP -======== - -This service lets you publish push and commit messages to a message broker -(like RabbitMQ) via the AMQP protocol. - - -Install Notes -------------- - - 1. Host is the hostname of the AMQP broker - 2. Port is the port to connect (AMQP default is 5672) - 3. Vhost is the vhost to use while publishing (default is '/') - 4. Exchange is the exchange to use while publishing - * Note that the exchange is a durable topic exchange - ** Future versions may allow you to configure this - 5. Username is the user to use when publishing to the exchange - 6. Password is the pasword to use when publishing to the exchange - * Note that SSL isn't supported yet - - Messages are sent for a push with the following routing key format: - "github.push.#{owner}.#{repo}.#{ref}" - where: - owner = payload['repository']['owner']['name'] - repo = payload['repository']['name'] - ref = payload['ref_name'] - - Messages are also sent for each commit in a push, with the following routing - key format: - "github.commit.#{owner}.#{repo}.#{ref}.#{author}" - where: - author = commit['author']['email'] - (other fields are the same as above) - - -Developer Notes ---------------- - -data - - host - - port - - vhost - - exchange - - username - - password - -payload - - refer to docs/github_payload diff --git a/docs/apiary b/docs/apiary new file mode 100644 index 000000000..6d169a8c7 --- /dev/null +++ b/docs/apiary @@ -0,0 +1,10 @@ +[Apiary.io](http://apiary.io/) is a REST API documentation, mocking and testing service. This service hook auto-updates your hosted API documentation generated from [API Blueprint](http://apiary.io/blueprint) in your repository. See more at [http://apiary.io/](http://apiary.io/) + +Install Notes +------------- + +Apiary needs your OAuth token in order to download your `apiary.apib` (that describes your API) file from your repository. Set up this service by: + +1. Go to [Apiary Settings](http://apiary.io/settings) +2. Click the **Connect to GitHub** button +3. _(optional)_ After completing the steps above you can always edit `Branch` field right here. Commits to this GitHub branch will trigger an update at Apiary diff --git a/docs/appharbor b/docs/appharbor index a877a6569..08ffe8e21 100644 --- a/docs/appharbor +++ b/docs/appharbor @@ -1,15 +1,10 @@ -AppHarbor -======= - AppHarbor is a .NET Platform as a Service. Use this service to automatically trigger build, test run and deployment of your AppHarbor application when you push to GitHub. Install Notes ------------- - 1. Go to your application's main page on AppHarbor and find the "Create build URL". Example: https://appharbor.com/application/{application_slug}/build?authorization={token} - - 2. "Token" is the value of the "authorization" parameter. - - 3. "Application Slugs" is a list of unique application identifiers delimited by "," (i.e. "foo" or "foo,bar") that pushes should trigger builds on. +1. Go to your application's main page on AppHarbor and find the "Create build URL". Example: https://appharbor.com/application/{application_slug}/build?authorization={token} +2. "token" is the value of the "authorization" parameter. +3. "application_slug" is a list of unique application identifiers delimited by "," (i.e. "foo" or "foo,bar") that pushes should trigger builds on. +4. If your GitHub repository is private you need to add the "apphb" GitHub user as a collaborator. This enables AppHarbor to download the source code. - 4. If your GitHub repository is private you need to add the "apphb" GitHub user as a collaborator. This enables AppHarbor to download the source code. diff --git a/docs/apropos b/docs/apropos new file mode 100644 index 000000000..4fdd8fd8e --- /dev/null +++ b/docs/apropos @@ -0,0 +1,5 @@ +Install Notes +------------- + +Use the project identifier from your Apropos project. It will be right below the project name +on the project page. It will look something like: GEZDGORQFY2TCNZRGY2TSMBVGUYDK \ No newline at end of file diff --git a/docs/asana b/docs/asana new file mode 100644 index 000000000..613e83ebf --- /dev/null +++ b/docs/asana @@ -0,0 +1,20 @@ +This service adds commit messages as comments to Asana tasks. Once enabled, commit messages +are checked for Asana task URLs (for example, https://app.asana.com/0/12345678/9012345) or task IDs +starting with # (for example, #9012345). Every task ID found will get the commit comment added to it. + +A commit with the message: + +``` +Fixes layout rendering bug. Reported in https://app.asana.com/0/12345678/9012345 +``` + +will show up as: +``` pushed to branch of / +(https://github.com///commit/) +-Fixes layout rendering bug. Reported in https://app.asana.com/0/12345678/9012345``` + +Install Notes +------------- +1. **Auth Token** - User API token. User must have access to task, all comments will be attributed to this user. See: https://developer.asana.com/documentation/#Authentication +2. **Restrict to Branch** - Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. +3. **Restrict to Last Commit** - Will only inspect the last commit of each push for task IDs. diff --git a/docs/autodeploy b/docs/autodeploy new file mode 100644 index 000000000..aebe58148 --- /dev/null +++ b/docs/autodeploy @@ -0,0 +1,16 @@ +This service automatically creates deployments based on your [workflow](http://www.atmos.org/github-services/auto-deployment/). + +By enabling this hook, GitHub will request a [GitHub Deployment][1] when the default branch is pushed to or your tests suite passes certain criteria. + +If you use Continuous Integration and GitHub you can select to "Deploy on Status" which will only create deployments when the default branch receives a "success" status for a commit. + +Install Notes +------------- + +1. `github_token` A [personal access token](https://github.com/settings/applications) from GitHub with `repo_deployment` scope. +2. `environments` A comma delimited set of environment names to auto-deploy. e.g. production,staging. +3. `deploy_on_status`: If checked deployments will only be created when successful [commit statuses][2] are created by your continuous integration system. +4. `github_api_url` The URL for the GitHub API. Override this for enterprise. **Optional** e.g. `https://enterprise.myorg.com`. + +[1]: https://developer.github.com/v3/repos/deployments +[2]: https://developer.github.com/v3/repos/statuses diff --git a/docs/awscodedeploy b/docs/awscodedeploy new file mode 100644 index 000000000..1a101438d --- /dev/null +++ b/docs/awscodedeploy @@ -0,0 +1,44 @@ +This service lets you deploy apps in [AWS CodeDeploy][1] when Deployments are created via the API. + +You need to provide an AWS access key id and the corresponding secret access key having at least the permission for the `codedeploy:CreateDeployment` action. This is the minimal required policy file: + +Note: You will need to replace β€œus-east-1” if you are using a different region, and replace β€œ123ACCOUNTID” with your AWS account ID that is found on your Account Settings page. +``` +{ + "Statement": [ + { + "Effect": "Allow", + "Action": "codedeploy:GetDeploymentConfig", + "Resource": "arn:aws:codedeploy:us-east-1:123ACCOUNTID:deploymentconfig:*" + }, + { + "Effect": "Allow", + "Action": "codedeploy:RegisterApplicationRevision", + "Resource": "arn:aws:codedeploy:us-east-1:123ACCOUNTID:application:DemoApplication" + }, + { + "Effect": "Allow", + "Action": "codedeploy:GetApplicationRevision", + "Resource": "arn:aws:codedeploy:us-east-1:123ACCOUNTID:application:DemoApplication" + }, + { + "Effect": "Allow", + "Action": "codedeploy:CreateDeployment", + "Resource": "arn:aws:codedeploy:us-east-1:123ACCOUNTID:deploymentgroup:DemoApplication/DemoFleet" + } + ] +} +``` + +Install Notes +------------- + +1. **Application Name** (required) - "CodeDeploy Application Name" on the **applications page** in the AWS CodeDeploy Console. +2. **Deployment Group** (Optional) - "Deployment Group" on the **app setting page** in the AWS CodeDeploy Console. Defaults to production. +3. **Aws Access Key Id** (required) - Access key id of an AWS IAM user having the permission for the `codedeploy:CreateDeployment` action. +4. **Aws Secret Access Key** (required) - Corresponding secret access key of the AWS IAM user. +5. **Aws Region** (Optional) - The region your servers are in. +6. **GitHub Token** (Optional) - A GitHub personal access token with `repo` scope to for OAuth cloning and deployment statuses for the GitHub API. +7. **GitHub API Url** (Optional) - The URL for the GitHub API. Override this for enterprise. e.g. `https://enterprise.myorg.com`. + +[1]: https://console.aws.amazon.com/codedeploy/home diff --git a/docs/awsopsworks b/docs/awsopsworks new file mode 100644 index 000000000..4d7420a36 --- /dev/null +++ b/docs/awsopsworks @@ -0,0 +1,32 @@ +This service lets you deploy apps in AWS OpsWorks after pushing to the configured branch. It will also pick up on deployment events and update the app to deploy to the requested sha. + +You need to provide an AWS access key id and the corresponding secret access key having at least the permission for the `opsworks:CreateDeployment` action. That's the minimal required policy file: + +``` +{ + "Statement": [ + { + "Effect": "Allow", + "Action": "opsworks:CreateDeployment", + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": "opsworks:UpdateApp", + "Resource": "*" + } + ] +} +``` + +Install Notes +------------- + +1. **Stack Id** (required) - "OpsWorks ID" on the **stack setting page** in the AWS OpsWorks Console or see `StackId` at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_Stack.html +2. **App Id** (required) - "OpsWorks ID" on the **app setting page** in the AWS OpsWorks Console or see `AppId` at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_App.html +3. **Branch Name** (required) - "Branch/Revision" configured for that app in the AWS OpsWorks Console or see `Revision` at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_Source.html +4. **Aws Access Key Id** (required) - Access key id of an AWS IAM user having the permission for the `opsworks:CreateDeployment` action. +5. **Aws Secret Access Key** (required) - Corresponding secret access key of the AWS IAM user. +6. **Endpoint Region** (Optional) - The API endpoint region for your stack. Defaults to us-east-1 (classic) +6. **GitHub Token** (Optional) - A GitHub personal access token with `repo` scope to for OAuth cloning and deployment statuses for the GitHub API. +7. **GitHub API Url** (Optional) - The URL for the GitHub API. Override this for enterprise. e.g. `https://enterprise.myorg.com`. diff --git a/docs/backlog b/docs/backlog new file mode 100644 index 000000000..8108023c3 --- /dev/null +++ b/docs/backlog @@ -0,0 +1,11 @@ +Backlog is a project management, hosting service. +http://backlogtool.com, http://www.backlog.jp (Japanese) + +Install Notes +------------- + +1. **api_url** is a API URL. e.g. if the Space ID is example, then the api_url will be https://example.backlog.jp/XML-RPC or https://example.backlogtoo.com/XML-RPC + +2. **user_id** and **password** - user_id and password of a Backlog user that can post comment and update issue. + +You can specify issue keys (e.g. DORA-1) and status keywords (#fixed, #closed) to change issue status (Resolved, Closed). diff --git a/docs/bamboo b/docs/bamboo index 9efcc59da..87fc3f746 100644 --- a/docs/bamboo +++ b/docs/bamboo @@ -1,38 +1,23 @@ -Bamboo -======= - Bamboo is a continuous integration server that automates the building and testing of your software. -The github Bamboo service can be used to trigger builds after code has been pushed to your git repository. +The GitHub Bamboo service can be used to trigger builds after code has been pushed to your git repository. Install Notes ------------- - 1. Your Bamboo server must be accessible from the internet. - - 2. "base_url" is the URL to your Bamboo server - Example: https://bamboo.example.com/ or http://bamboo.example.com/bamboo/ - - 3. "build_key" is the identifier of the plan you want to trigger - Example: "BAM-TRUNK", where BAM = project key, TRUNK = plan key - - A compound build key value can be used to specify multiple builds or associate - specific branches with a build - Example: "master:BAM-TRUNK,3-2-patches:BAM-32PATCH,BAM-TESTS", where BAM-TRUNK - will be triggered only by pushes to the master branch, BAM-32PATCH will only - be triggered by pushes to the 3-2-patches branch, and BAM-TESTS will be triggered - for any push. +1. Your Bamboo server must be accessible from the internet. - 4. "username" and "password" - username and password of a Bamboo user that can - trigger a manual build of the Bamboo plan defined in "build_key" +2. "base_url" is the URL to your Bamboo server + Example: https://bamboo.example.com/ or http://bamboo.example.com/bamboo/ -Developer Notes ---------------- +3. "build_key" is the identifier of the plan you want to trigger + Example: "BAM-TRUNK", where BAM = project key, TRUNK = plan key -data - - base_url - - build_key - - username - - password + A compound build key value can be used to specify multiple builds or associate + specific branches with a build + Example: "master:BAM-TRUNK,3-2-patches:BAM-32PATCH,BAM-TESTS", where BAM-TRUNK + will be triggered only by pushes to the master branch, BAM-32PATCH will only + be triggered by pushes to the 3-2-patches branch, and BAM-TESTS will be triggered + for any push. -payload - - refer to docs/github_payload +4. "username" and "password" - username and password of a Bamboo user that can + trigger a manual build of the Bamboo plan defined in "build_key" diff --git a/docs/basecamp b/docs/basecamp index 65098285b..87ef55f8a 100644 --- a/docs/basecamp +++ b/docs/basecamp @@ -1,27 +1,6 @@ -Basecamp -======== - Install Notes ------------- - 1. url should be your Basecamp url - 2. username should be the username or API token of the user that you want to use to post messages into your Basecamp - you can setup a user just for this purpose - 3. password should be the password of the user that you want to use to post the messages. If username is an API token, set password to 'x'. - 4. project should be the name of the project that you want to post the message into (not the id) - 5. category should be the name of the category that you want to post the message using (not the id) - 6. ssl should be enabled for accounts that need SSL. - - -Developer Notes ---------------- - -data - - url - - username - - password - - project - - category - - ssl - -payload - - refer to docs/github_payload \ No newline at end of file + 1. project_url is the URL of your Basecamp project: https://basecamp.com/1234/projects/5678 + 2. email_address is the email you sign in to Basecamp with. This person must have access to the project. To add events on behalf of other people, make the person an admin on the project. + 3. password is the password you sign in to Basecamp with. diff --git a/docs/boxcar b/docs/boxcar deleted file mode 100644 index 8ac9a7f09..000000000 --- a/docs/boxcar +++ /dev/null @@ -1,21 +0,0 @@ -Boxcar -========== - -Send your commits to Boxcar, a messaging platform. Get instant commit messages on your desktop and mobile devices. - -Install Notes -------------- - - 1. Download and install Boxcar from http://boxcar.io - 2. Type in the Boxcar e-mail addresses of the users wanting to be notified of commits to this repository. - 3. If the user does not yet have Boxcar, we'll send them an e-mail to let them know where they can get it. - - -Developer Notes ---------------- - -data - - subscribers (csv list of e-mail addresses) - -payload - - refer to docs/github_payload diff --git a/docs/bugherd b/docs/bugherd index edc7a4c78..3635d7859 100644 --- a/docs/bugherd +++ b/docs/bugherd @@ -1,12 +1,3 @@ -BugHerd -======= - BugHerd is the world's simplest bug tracker for the web. See: http://www.bugherd.com BugHerd allows you to visually track issues on your website. Using the GitHub integration you can update tasks directly from GitHub commit messages. - - -Install Notes -------------- - - 1. project key -- you will find this key in BugHerd as you setup a GitHub integration in your project. For more info, see https://bugherd.tenderapp.com/kb/third-party-integrations/github diff --git a/docs/bugly b/docs/bugly deleted file mode 100644 index 4c638ba13..000000000 --- a/docs/bugly +++ /dev/null @@ -1,37 +0,0 @@ -Bugly -===== - -Bugly is a hosted bug and issue tracker. Integrating it with GitHub -allows you to associate commits with issues and see commits in all -timelines. - - -Install Notes -------------- - - 1. 'token' (REQUIRED) is an API token with read/write access to the - account/project. Click the '+' sign in the lower left corner of - your Bugly account, then click New token to create a new token - for your user. Or click Quick Access, then the user you wish to - create a token for, then click the API tokens tab, then click - New API token. - - 2. 'account_name' (REQUIRED) is your Bugly account name. It would be - 'foobar' in the following URL: http://foobar.bug.ly/ - - 3. 'project_id' (OPTIONAL) is the ID of the Bugly project to associate - all commits with. If no project_id is provided, commits will only - appear in the account-wide timeline, not project timelines. The ID - would be '321' in this URL: http://foo.bug.ly/projects/321-MyProject - - -Developer Notes ---------------- - -data - - token - - account_name - - project_id - -payload - - refer to docs/github_payload diff --git a/docs/bugzilla b/docs/bugzilla index a7c6adddf..341446973 100644 --- a/docs/bugzilla +++ b/docs/bugzilla @@ -1,6 +1,3 @@ -Bugzilla -======== - This service hook posts comments on Bugzilla bugs when commit messages reference a bug by id number. @@ -24,6 +21,12 @@ branch: you probably want to set this to "master". If the central repository option is enabled and there is a "fix", "close", or "address" before the bug then that bug is closed. +When using the close bug feature you should disable +`commentonchange_resolution` in Bugzilla's configuration. Without this option +disabled when someone set bugs to `RESOLVED FIXED` status it will fail with +the message `You have to specify a comment when changing the Resolution of a +bug from (empty) to FIXED` + If a commit has already been mentioned in the bug comments when pushed by another user then a comment isn't made, unless the central repository option is enabled. In this case only the first line of the commit message @@ -31,15 +34,3 @@ is included in the bug comment. This hook requires Bugzilla version >= 3.4 to function correctly and >= 4.0 to be able to close bugs. - -Settings --------- - - - server_url - The URL for the Bugzilla installation, - eg `https://bugzilla.mozilla.org/` - - username - Email address for Bugzilla user account - - password - Password for the user account - - integration_branch - If entered, only commits to this branch will result - in bugs being closed - - central_repository - Set to true to enable closing bugs and commenting on - commits that were already pushed to another user's repository diff --git a/docs/campfire b/docs/campfire index 66a571a67..b757a9c12 100644 --- a/docs/campfire +++ b/docs/campfire @@ -1,11 +1,8 @@ -Campfire -======== - Install Notes ------------- 1. subdomain is your campfire subdomain - (ie 'your-subdomain' if you visit 'https://your-subdomain.campfirenow.com') + (ie 'your-subdomain' if you visit 'https://your-subdomain.campfirenow.com') 2. room is the actual room name, not the id @@ -17,16 +14,5 @@ Install Notes 5. play_sound is a boolean flag indicating whether a sound should be played - -Developer Notes ---------------- - -data - - subdomain - - token - - room - - master_only - - play_sound - -payload - - refer to docs/github_payload + 6. sound is the sound that should be played if play_sound is + true. defaults to 'rimshot' diff --git a/docs/cia b/docs/cia index 76db054e4..7caef28c6 100644 --- a/docs/cia +++ b/docs/cia @@ -1,20 +1,8 @@ -CIA.vc -======== +1. Disable multiple messages if more than 5 commits are pushed. Instead announce the last commit with "(+## more commits...)" appended to the note. +2. `address` is the optional CIA address, defaults to http://cia.vc +3. `project` is the optional project name, defaults to the Repository name. +4. `branch` lets you specify the branch name, or a template with `%s` to fill the branch name in. +5. `long_urls` enables full URLs instead of shortened URLs. +6. `module` sets the module name in the project. +7. `full_commits` enables sending full commit messages to CIA (as opposed to just the first line). -Install Notes -------------- - - 1. Disable multiple messages if more than 5 commits are pushed. Instead announce the last commit with "(+## more commits...)" appended to the note. - - -Developer Notes ---------------- - -data - - no_spam - - project - Optional project name, defaults to the Repository name. - - address - Optional CIA address, defaults to http://cia.vc - - branch - Optional branch template, use %s to include the real branch name - -payload - - refer to docs/github_payload diff --git a/docs/co_op b/docs/co_op deleted file mode 100644 index 03cf2d402..000000000 --- a/docs/co_op +++ /dev/null @@ -1,22 +0,0 @@ -Co-op -======== - -Install Notes -------------- - - 1. group_id is in the URL for your group, for example if your URL is "http://coopapp.com/groups/1066", your group_id is 1066 - - 2. token is your API token. You can find this token in the group management panel as the group administrator, eg: "http://coopapp.com/groups/1066/edit" - - 3. Commit messages show up as being from Cobot, the Co-op message robot - - -Developer Notes ---------------- - -data - - group_id - - token - -payload - - refer to docs/github_payload diff --git a/docs/codeclimate b/docs/codeclimate index 9a180cfa9..e2e493af6 100644 --- a/docs/codeclimate +++ b/docs/codeclimate @@ -1,19 +1,20 @@ -Code Climate -============ - Install Notes ------------- -1. Create an account at https://codeclimate.com -2. Enter your Token (see instructions below) +To set up this service hook, follow these steps: + +1. Browse to your Code Climate [Dashboard](https://codeclimate.com/dashboard). +2. Click **Organization** in the top-right corner of the page. If this link isn't visible, see below. +3. Select the **Integrations** tab. +4. Copy the **GitHub Service Token** to your clipboard. Note that you do not want to copy the **API Token**. +5. Back in GitHub, paste the token into the text box below. -To get your Token: Log into your Code Climate account, click the gear icon at the top-right, then click the Integration tab. +### Why don't I see the Organization link in Code Climate? -Developer Notes ---------------- +Few possible reasons: -data - - token +* You're trying to set up a service hook for a repository that lives in the **Open Source** section of your **Dashboard**. Unfortunately we don't yet support service hooks for these repositories. +* Your Code Climate user is not a member of any organizations. +* You're in an organization but not in its **Owners** group. In this case, you unfortunately won't have administrative-rights to view this token. -payload - - refer to docs/github_payload +For more detailed info, see our [help article](http://docs.codeclimate.com/article/222-how-do-i-install-code-climates-github-service-hook). diff --git a/docs/codereviewhub b/docs/codereviewhub new file mode 100644 index 000000000..74ea34792 --- /dev/null +++ b/docs/codereviewhub @@ -0,0 +1,17 @@ +[CodeReviewHub](https://www.codereviewhub.com) will add these hooks automatically to projects which you enable on www.codereviewhub.com + +GitHub Code Reviews made easy +----------------------------- + +- Keep track of unaddressed comments. +- Keep track of open issues. +- No more lost file comments due to changing diffs! + +Install Notes +------------- + +1. Sign up at https://www.codereviewhub.com with your GitHub account +2. Add your repository +3. You're ready! + +For more details about CodeReviewHub, go to https://www.codereviewhub.com. diff --git a/docs/codeship b/docs/codeship new file mode 100644 index 000000000..960ea7e7a --- /dev/null +++ b/docs/codeship @@ -0,0 +1,19 @@ +Codeship offers continuous integration and deployment for a variety of applications including Ruby, Node, PHP, Python, Java and Go. + +This service hook will inform Codeship every time you push to GitHub. On every push Codeship tests the current version of your application. If all tests succeed, Codeship can also deploy your application, otherwise it informs you about the failures. + +Install Notes +------------- + +Codeship will install this hook automatically on sign up. However, if creating the service didn't succeed or you accidentally deleted it, you can also install it manually. + +1. Sign up at [codeship.com](https://codeship.com) +2. Go to your project's settings, find the General section and copy the project UUID +3. Paste the project UUID into the text field below +4. Make sure the "Active" checkbox is ticked and click "Update service" +5. Click on the "Codeship" service name and then click "Test service" +6. Now there should be a running build on your Codeship dashboard + +Learn more by visiting [codeship.com](https://codeship.com) and our [documentation](https://documentation.codeship.com). + +[Contact us](https://helpdesk.codeship.com) if you need further assistance. diff --git a/docs/commandoio b/docs/commandoio new file mode 100644 index 000000000..6ebef16ac --- /dev/null +++ b/docs/commandoio @@ -0,0 +1,12 @@ +[Commando.io](https://commando.io) A simpler way to manage servers online. Commando.io; your first DevOps hire! + +Install Notes +------------- + + * **API token secret key** - A valid API token secret key. You may create API tokens on the settings page in the Commando.io web interface. + * **Account alias** - Your account alias is simply the subdomain that you access Commando.io with. For example the account alias of `https://foo.commando.io` is `foo`. + * **Recipe** - The recipe you wish to execute. You may find recipe ids in the in the Commando.io web interface. + * **Server** - A single server id. You may find server ids in the modal popup when clicking a server in the Commando.io web interface. + * **Groups** - A list of group ids separated by commas. You may find group ids in the modal popup when clicking a group in the Commando.io web interface. + * **Notes** _(Optional)_ - Notes and comments you wish to attach to this execution. Markdown is supported. + * **Halt on stderr** _(Optional)_ - If a server returns stderr during execution, halt and prevent the remaining servers from executing the recipe. diff --git a/docs/conductor b/docs/conductor new file mode 100644 index 000000000..d675c3d68 --- /dev/null +++ b/docs/conductor @@ -0,0 +1,5 @@ +Install Notes +------------- + 1. **API Key** is from within the Conductor project. (https://conductor-app.com/) + 2. This service will post back all pushes, regardless of whether or not they contain ticket annotations. + 3. Annotations will be parsed for [#xxx] content, where xxx denotes a valid ticket number in the relevant Conductor project diff --git a/docs/convore b/docs/convore deleted file mode 100644 index ef8d4bc48..000000000 --- a/docs/convore +++ /dev/null @@ -1,23 +0,0 @@ -Convore -======== - -Install Notes -------------- - - 1. `topic_id` is the topic you'd like to post into. You'll need to use the - API to walk down through your groups, then your desired group's topics. - - 2. `username` and `password` should come from a user you create just for - this service hook. - - -Developer Notes ---------------- - -data - - topic - - username - - password - -payload - - refer to docs/github_payload diff --git a/docs/copperegg b/docs/copperegg new file mode 100644 index 000000000..089c001e0 --- /dev/null +++ b/docs/copperegg @@ -0,0 +1,12 @@ +Allows you to setup a GitHub repository to create Annotations on git pushes. +Annotations are created like so: + + "GitHub: #{payload['pusher']['name']} has pushed + #{payload['commits'].size} commit(s) to + #{payload['repository']['name']}" + +Install Notes +------------- +1. **API Key** - Your API key. See: Settings tab, Personal Settings: https://app.copperegg.com/ +2. **Tag** - Tag to automatically apply to each created Annotation. +3. **Master Only** - Only create Annotations for pushes to the master branch. diff --git a/docs/crocagile b/docs/crocagile new file mode 100644 index 000000000..5d130425b --- /dev/null +++ b/docs/crocagile @@ -0,0 +1,35 @@ +Crocagile is an agile-inspired workspace that keeps your team together +throughout the entire product life-cycle. Learn more at https://www.crocagile.com + +GitHub integration supports progress updates and time logging for tasks via +commit message using "Updates" and "Time" triggers. Samples below. + + +INSTALLATION +============= + +1. Obtain your Project Key from the settings area of your Crocagile project. +2. Save your project key to the Crocagile Service in your repository. +3. Use commit messages to update tasks. (See samples). +4. Visit https://blog.crocagile.com for friendly documentation. + + +SAMPLES +============= + +Update a task to 60% complete. +-m " Your commit message. Updates T2355=60" + +Update two tasks at once. (No spaces between tasks) +-m " Your commit message. Updates T2355=60,T2660=90" + +Mark a task complete and log time in hours (supports float). +-m " Your commit message. Updates T2355=100 Time T2355=2.5" + +Update multiple progress+time (Spaces permitted between triggers, Not between tasks) +-m "Your commit message. Updates T2355=100,T1039=75 Time T2355=2,T1039=1.5" + +Notes: +"Update T2355" is identical to "Update T2355=100" +"Time T2355" is identical to "Time T2355=1" +Don't forget the "T" ! diff --git a/docs/cube b/docs/cube deleted file mode 100644 index eb45b9f63..000000000 --- a/docs/cube +++ /dev/null @@ -1,23 +0,0 @@ -Cube -========== - -Cube is a timetracking app for individuals or companies; for more details see http://cube.bitrzr.com -This service lets your broadcast commits on Cube so they can be seen in the unified project log. - -Install Notes -------------- - - 1. domain (company domain for Google Apps account or user email address for individual accounts) - 2. project (project name) - 3. token (project integration token as seen in the project's Settings tab) - -Developer Notes ---------------- - -data - - domain - - project - - token - -payload - - refer to docs/github_payload diff --git a/docs/deployervc b/docs/deployervc new file mode 100644 index 000000000..1b3ad5696 --- /dev/null +++ b/docs/deployervc @@ -0,0 +1,7 @@ +This service hook triggers a deployment on deployer.vc whenever a push is made. + +Install Notes +------------- + + 1. **Deployment Address** - URL of the deployment to be triggered whenever a commit/push occurs, normally in the format `https://.deployer.vc/#/deployment/` + 2. **API Token** - An API token for your deployer.vc account, which can be created under `https://.deployer.vc/#/account` \ No newline at end of file diff --git a/docs/deployhq b/docs/deployhq new file mode 100644 index 000000000..11bf70ab3 --- /dev/null +++ b/docs/deployhq @@ -0,0 +1,14 @@ +DeployHQ is a service which lets you deploy directly +from your GitHub repository to your server via FTP or +SSH/SFTP. + +Install Notes +------------- + +1. **Deploy Hook URL** Should be the automatic deployment URL of the +server you wish to deploy to. You can find this on the +Edit Server or Edit Server Group page in DeployHQ. + +1. **Email Pusher** Specifies whether the person who pushed +the commits should receive an email once a deployment +is completed. diff --git a/docs/divecloud b/docs/divecloud new file mode 100644 index 000000000..3275eedbf --- /dev/null +++ b/docs/divecloud @@ -0,0 +1,9 @@ +DiveCloud integrates Application Performance Testing into the Continuous Integration Cycle by allowing you to run a performance test after each successful deployment. + +When GitHub receives notice that you have successfully deployed your application, it will initiate a performance test based on the test plan you created at https://divecloud.nouvola.com. + +Please enter your API Key and the Plan ID for the plan that you would like to run on successful deployment of your application. + +If you would like to add 'Think Time' to you test, select the 'Think Time' checkbox. + +If your test plan includes credentials, include your credential password in the field below. diff --git a/docs/djangopackages b/docs/djangopackages new file mode 100644 index 000000000..bb77adf46 --- /dev/null +++ b/docs/djangopackages @@ -0,0 +1 @@ +Automatically update commit history as tracked on djangopackages.com. \ No newline at end of file diff --git a/docs/docker b/docs/docker new file mode 100644 index 000000000..4b71ec1c7 --- /dev/null +++ b/docs/docker @@ -0,0 +1,25 @@ +When enabled this will let the Docker Hub know when you have made changes to +your repository. The Docker Hub will then pull down your repository and build +your Dockerfile to create a Docker repository and push it onto the Docker Hub +so that it is available for others to download. When you commit changes to your +git Repo the Docker Hub will keep the Docker Repository up to date. + +#### Note: + +Docker has two types of repositories, public and private. A public repository +is able to be downloaded by anyone. The private repository can only be +downloaded by someone whom the owner has given explicit access too. + +When creating an automated build from a private GitHub repo, it is recommended +that you only use a private Docker repo. If you have a private GitHub +repository and you are building into a public Docker repository, you might be +accidentally exposing data you don't intend to expose. + +#### Requirements: + +In order for this to work correctly, you should have the following: + +1. A [Docker Hub](https://hub.docker.com "Docker Hub") + Account, that is linked to your GitHub account. +2. A [Dockerfile](https://docs.docker.com/reference/builder/ "Dockerfile") + in your project directory. diff --git a/docs/ducksboard b/docs/ducksboard deleted file mode 100644 index 5899d55ca..000000000 --- a/docs/ducksboard +++ /dev/null @@ -1,13 +0,0 @@ -Ducksboard -========== - -Get real-time notifications for pushes, issues, forks and watches in your Ducksboard GitHub widgets. - -More info at http://ducksboard.com/ - -Install Notes -------------- - -See https://ducksboard.jira.com/wiki/display/webhooks/GitHub for detailed instructions. - -The required "Webhook Key" can be found in the Ducksboard widget settings, under the "Advanced Settings" tab. Up to 5 different keys, separated by SPACES, can be provided if many widgets must be updated from this repository. diff --git a/docs/email b/docs/email index 6f8811b70..72a3ce23a 100644 --- a/docs/email +++ b/docs/email @@ -1,21 +1,7 @@ -Email -======== - Install Notes ------------- -1. `address(es)` whitespace separated email addresses (at most two) +1. `address` whitespace separated email addresses (at most two) 2. `secret` fills out the Approved header to automatically approve the message in a read-only or moderated mailing list. 3. `send_from_author` uses the commit author email address in the From address of the email. - -Developer Notes ---------------- - -data - - address(es) - - secret - - send_from_author - -payload - - refer to docs/github_payload diff --git a/docs/firebase b/docs/firebase new file mode 100644 index 000000000..24d92a3ad --- /dev/null +++ b/docs/firebase @@ -0,0 +1,15 @@ +This service hook adds an object to a given Firebase for every commit. + +The commit will be appended, in-order, to the URL provided in the 'firebase' +configuration option. This is the equivalent of calling: + + new Firebase(firebase).push(commit); + +from the JS library. + +The provided URL must point to a valid Firebase reference, and must be prefixed +with https:// + +If you have secured your Firebase with security rules that prevent anonymous +writes, you may provide the secret to your firebase in the configuration +options. diff --git a/docs/fisheye b/docs/fisheye new file mode 100644 index 000000000..b95c1eb7a --- /dev/null +++ b/docs/fisheye @@ -0,0 +1,18 @@ +Atlassian FishEye is a revision-control browser and search engine. +The GitHub FishEye service can be used to trigger repository scan after code has been pushed to your git repository. + +Requires FishEye 2.10 or later. + +Install Notes +------------- + +1. Your FishEye server must be accessible from the internet + +2. "url_base" is the URL to your FishEye server + Example: http://fisheye.example.com/ or http://fisheye.example.com/fisheye/ + +3. "token" is the REST API token for your FishEye server + It is defined in FishEye Administration > Security Settings > Authentication > REST API Token + +4. "repository_name" is the repository name (optional) + If not set GitHub repository name is used diff --git a/docs/flowdock b/docs/flowdock index 5515ecb6d..6198c195c 100644 --- a/docs/flowdock +++ b/docs/flowdock @@ -1,7 +1,4 @@ -Flowdock -========== - -Broadcast this project's commits, pull requests, and issues and their +Broadcast this project's commits, pull requests, issues, and their respective comments to Flowdock. Install Notes @@ -9,12 +6,11 @@ Install Notes API Token is a flow-specific token. You can find it either in the flow, or in Flowdock's Integrations help page at https://www.flowdock.com/help/integrations. + Multiple flows may be specified with multiple comma-separated tokens. -Developer Notes ---------------- + To include certain tags with every message, you can add tags to the tokens. Use + to separate tags. For example, "65f80e994031fcd80f57d1a9e294c1d1d+frontend+web,af8629b07978d1223c9d48d05ff1356+fyi" would add `frontend` and `web` tags to the messages posted to the first flow, and `fyi` to the messages posted to the second flow. -data - - token +Configure From Flowdock +----------------------- -payload - - refer to docs/github_payload + You can also configure hooks from within Flowdock. Those hooks will appear in the `Web Hooks` category of the service hooks. This can be done from the flow’s inbox settings. diff --git a/docs/fog_bugz b/docs/fogbugz similarity index 70% rename from docs/fog_bugz rename to docs/fogbugz index 38588ec62..ed0e34b6e 100644 --- a/docs/fog_bugz +++ b/docs/fogbugz @@ -1,13 +1,10 @@ -FogBugz -======= - Install Notes ------------- 1. Requires FogBugz version 6.1 or above, and your FogBugz installation must be accessible from the internet - 2. "cvssubmit_url" is the url to the cvsSubmit.[php|asp] file on your + 2. "cvssubmit_url" is the url to the `cvsSubmit.[php|asp]` file on your FogBugz server. Example: https://server.com/fogbugz/cvsSubmit.php @@ -20,25 +17,22 @@ Install Notes FogBugz 7.0 and later Configuration --------------- -Starting in FogBugz 7, there is a configuration page where you can setup -multiple repositories. Each repository is given and ID that will be used -by the cvsSubmit.asp page. - -In FogBugz: +FogBugz 7 has a configuration page for multiple repositories. Each repository +is given and ID that will be used by the cvsSubmit.asp page. 1. From the admin menu, select Source Control. 2. Click 'Create New Repository'. - 3. An option pane will pop up. Select 'Other (custom)'. Enter a name for - the repository. The name does not have to be the same as the github repo + 3. An option pane will pop up. Select 'Other (custom)'. Enter a name for + the repository. The name does not have to be the same as the GitHub repo name. Click Next. 4. Set the 'Diff URL' field to be: - https://github.com/[github_username]/[github_reponame]/commit/^R2 + `https://github.com/[github_username]/[github_reponame]/commit/^R2` 5. Set the 'Log URL' field to be: - https://github.com/[github_username]/[github_reponame]/commits/^FILE + `https://github.com/[github_username]/[github_reponame]/commits/^FILE` 6. There's a URL printed at the top of the configuration screen. Make note of the number following ixRepository= That is the ID that needs @@ -56,7 +50,7 @@ FogBugz 6.1 Configuration --------------------- If you want to use GitHub as your sole source control system, configuration -within FogBugz is relatively easy. +within FogBugz is relatively easy. In your site configuration: @@ -69,8 +63,6 @@ In your site configuration: If you have commits in FogBugz from a previous source control system, or if you want to use multiple GitHub accounts, please read: -http://www.fogcreek.com/FogBugz/KB/howto/MultipleRepositories-Mult.html - In the service hook here: 1. Set your Cvssubmit Url. @@ -79,13 +71,16 @@ In the service hook here: 3. Leave "Fb Repoid" blank. -Developer Notes ---------------- + + + +Usage +----- + +The commit message will be added as a note to the case. You cannot resolve a case via the commit message. +The hook parses the commit messages for bug ids based on the below syntax. -data - - cvssubmit_url - - fb_version (Valid options are 6 or anything else for 7, 8 or later) - - fb_repoid +Examples: -payload - - refer to docs/github_payload + * case(s): 12345, 67890, [additional] + * bug(s)ID: 12345, 67890, [additional] diff --git a/docs/freckle b/docs/freckle index 11adf0d86..038ef1e42 100644 --- a/docs/freckle +++ b/docs/freckle @@ -1,11 +1,7 @@ -freckle -======== - Install Notes ------------- -Using the following URL as an example: -https://nutsnbolts.letsfreckle.com +Using the following URL as an example: https://nutsnbolts.letsfreckle.com 1. Account name: subdomain of the freckle account (nutsnbolts) 2. Token: the API token for an individual freckle user who is associated with the target freckle account (see instructions below) @@ -13,15 +9,4 @@ https://nutsnbolts.letsfreckle.com To get your API token: log into your freckle account, click Settings at the top right, then click the Integration (API) tab. -Note about email addresses: the email address on a committer's GitHub account should match his/her email address on his/her freckle account. Otherwise, we can't identify the person who is logging time. - -Developer Notes ---------------- - -data - - subdomain - - token - - project name - -payload - - refer to docs/github_payload +Note about email addresses: the email address on a committer's GitHub account should match his/her email address on his/her freckle account. Otherwise, we can't identify the person who is logging time. \ No newline at end of file diff --git a/docs/friend_feed b/docs/friend_feed deleted file mode 100644 index 46ab95ae2..000000000 --- a/docs/friend_feed +++ /dev/null @@ -1,18 +0,0 @@ -FriendFeed -========== - -This service lets your broadcast your commits on FriendFeed. A simple post is made to http://www.friendfeed.com/api/share with your commits. - -Example: 'Arun Thampi just pushed 56436bcdef2342ddfca234234 to github-services on GitHub' -with the comment set as 'Integrated FriendFeed in github-services' - - -Developer Notes ---------------- - -data - - nickname - - remotekey [As specified by http://www.friendfeed.com/remotekey] - -payload - - refer to docs/github_payload diff --git a/docs/gemini b/docs/gemini new file mode 100644 index 000000000..dd8aca7a5 --- /dev/null +++ b/docs/gemini @@ -0,0 +1,11 @@ +IT'S ABOUT TRACKING +Gemini is the all-in-one Tracker that works beautifully for any kind of project, team or process. +Every task, initiative, bug, issue, enquiry, change request and ticket in one place. + +This GitHub integration works with Gemini v5.1.2+. + +Install Notes +------------- + +* Create an API Key(GUID) in your Web.config file for the key "gemini.apikey". +* "gemini_url" is YOUR Gemini instance i.e "http://gemini.countersoft.com". diff --git a/docs/gemnasium b/docs/gemnasium index 2ea48afd8..abd355a38 100644 --- a/docs/gemnasium +++ b/docs/gemnasium @@ -1,9 +1,3 @@ -Gemnasium -========= - -Install Notes -------------- - Gemnasium notifies you when new versions are released for your GitHub repositories' gem dependencies. ---- @@ -14,9 +8,3 @@ Gemnasium notifies you when new versions are released for your GitHub repositori 4. Check "Active" 5. Click "Update Settings" -Developer Notes ---------------- - - data - - name - - token diff --git a/docs/geocommit b/docs/geocommit deleted file mode 100644 index 8b6a37c2f..000000000 --- a/docs/geocommit +++ /dev/null @@ -1,18 +0,0 @@ -Geocommit -========== - -With this service you can send your commits to geocommit. Geocommit analyzes geographical geocommit information in git notes and allows you to visualize your contributions geographically. - -Install Notes -------------- - - 1. Profit! - -Developer Notes ---------------- - -data - - none - -payload - - refer to docs/github_payload diff --git a/docs/get_localization b/docs/getlocalization similarity index 59% rename from docs/get_localization rename to docs/getlocalization index 5427d39ef..5a2dcd04a 100644 --- a/docs/get_localization +++ b/docs/getlocalization @@ -1,9 +1,3 @@ -Get Localization -=============== - -Install Notes -------------- - 1. Project name is your project name in Get Localization URL e.g: Given this URL: @@ -13,13 +7,3 @@ Install Notes Your project name is "Example" 2. Project token is available at your project settings if you've GitHub support enabled. - -Developer Notes ---------------- - -data - - project_name - - project_token - -payload - - refer to docs/github_payload diff --git a/docs/gitlive b/docs/gitlive deleted file mode 100644 index 750034e44..000000000 --- a/docs/gitlive +++ /dev/null @@ -1,22 +0,0 @@ -gitlive -======= - -gitlive displays your GitHub pushes in realtime on your project homepage. See http://gitlive.com/ for more information. - -Install Notes -------------- - -1. Activate service - -2. Generate widget at http://gitlive.com/ for embedding on your project page. - -Note: Service is active after first push on GitHub. - -Developer Notes ---------------- - -data - - none - -payload - - refer to docs/github_payload diff --git a/docs/gitter b/docs/gitter new file mode 100644 index 000000000..85b8433a7 --- /dev/null +++ b/docs/gitter @@ -0,0 +1,23 @@ +Chat rooms for your public and private repos with awesome GitHub integration. + +https://gitter.im + +If you want to see detailed repo activity in your room, follow this steps: + + - Click the **Settings Cog** on the top right + - Click on **Integrations** + - Select **GitHub** + - Click on **configure manually** option + - Copy and paste the provided token here + +Install Notes +------------- + +1. **Token** - Gitter token for this repo + +Options + +2. **Mute Fork** - Mute fork notifications. +3. **Mute Watch** - Mute watch notifications. +4. **Mute Comments** - Mute issue and pull request comments. +5. **Mute Wiki** - Mute wiki changes. diff --git a/docs/gocd b/docs/gocd new file mode 100644 index 000000000..baff0c10e --- /dev/null +++ b/docs/gocd @@ -0,0 +1,21 @@ +[Go](http://go.cd) (also GoCD) is a Continuous Integration (CI) and Continuous Delivery (CD) server. + +The GitHub GoCD service can be used to trigger builds when changes are pushed to the corresponding GitHub repository. +This is a replacement for the polling method, where the Go server periodically polls the repositories for changes. + +Install Notes +------------- + +1. Your Go server must be accessible from the internet. + +2. "base_url" (mandatory) is the URL to your Go server + Example: https://go.example.com/ or http://go.example.com:8153 + +3. "repository_url" (mandatory) is the URL that is configured in the Materials section of the concerned pipelines. + Example: git@github.com:gocd/gocd.git or git://github.com/gocd/gocd + +4. "username" and "password" - username and password of a Go admin user that can + trigger the Materials API endpoint. Can be left empty if the Go server has no authentication configured (not recommended.) + +5. "verify_ssl" is used to enable (recommended) or disable certificate checking when using ssl. + Disabling this can make the ssl connection insecure. diff --git a/docs/grmble b/docs/grmble deleted file mode 100644 index 6f37d75ad..000000000 --- a/docs/grmble +++ /dev/null @@ -1,19 +0,0 @@ -Grmble -======== - -Install Notes -------------- - - 1. room_api_url should be your room's API url from your room's admin page - Eg: http://grmble.com/api/room/ROOMKEY - 2. token should be the API key from your room's admin page - -Developer Notes ---------------- - -data - - room_api_url - - token - -payload - - refer to docs/github_payload \ No newline at end of file diff --git a/docs/grove b/docs/grove index c6f0c0b5a..1f477db64 100644 --- a/docs/grove +++ b/docs/grove @@ -1,18 +1,7 @@ -Grove.io -========= - -Post deploy messages to Grove.io's IRC channels. +This delivers updates to your GitHub repository to Grove.io's IRC channels. +Events include Git pushes, issues, wiki changes, and comments. Install Notes ------------- - 1. `channel_token` - Channel's unique token which can be found from channel settings (available for organization owners) - -Developer Notes ---------------- - -data - - channel_token - -payload - - refer to docs/github_payload + `channel_token` - Channel's unique token which can be found from channel settings (available for organization owners) diff --git a/docs/habitualist b/docs/habitualist new file mode 100644 index 000000000..1836ae1d9 --- /dev/null +++ b/docs/habitualist @@ -0,0 +1 @@ +Automatically log commit activity against actions on https://habitualist.com diff --git a/docs/harvest b/docs/harvest index 27195a377..c26cd04ff 100644 --- a/docs/harvest +++ b/docs/harvest @@ -1,19 +1,4 @@ -Harvest -======== - Install Notes ------------- 1. Applies the status to the currently clocked in project. Future updates are expected to include more versatility. - -Developer Notes ---------------- - -data - - username (your email address) - - password (harvest password) - - subdomain (subdomain for your business ex. {subdomain}.harvestapp.com) - - ssl (boolean) - -payload - - refer to docs/github_payload diff --git a/docs/herokubeta b/docs/herokubeta new file mode 100644 index 000000000..ab018eb36 --- /dev/null +++ b/docs/herokubeta @@ -0,0 +1,17 @@ +[heroku](https://heroku.com) is a hosting platform with support for a large number of languages. + +By enabling this hook, GitHub will request a [heroku build](https://devcenter.heroku.com/articles/platform-api-reference#build-create) that pushes your code to a named heroku application when a [deployment event](https://developer.github.com/webhooks/#events) is received. + +To trigger deployment events you'll need to [create a deployment](https://developer.github.com/v3/repos/deployments/#create-a-deployment) with the API. + +There's a [hubot-deploy](https://github.com/atmos/hubot-deploy) script that allows you to deploy from chat. Heroku has various options for [deploy hooks](https://devcenter.heroku.com/articles/deploy-hooks) to give you feedback. + +The [auto-deployment](http://www.atmos.org/github-services/auto-deployment/) service exists for creating deployments automatically on push and successful CI runs. + +Install Notes +------------- + +1. `name` The application name on heroku to deploy to. +2. `heroku_token` A user or [direct authorization](https://devcenter.heroku.com/articles/oauth#direct-authorization) token from heroku. +3. `github_token` A [personal access token](https://github.com/settings/applications) from GitHub with the `repo` scope. We generate an [archive link](https://developer.github.com/v3/repos/contents/#get-archive-link) for the heroku API with it. +4. `github_api_url` The URL for the GitHub API. Override this for enterprise. **Optional** e.g. `https://enterprise.myorg.com`. diff --git a/docs/hipchat b/docs/hipchat index f5c71430b..d2811a916 100644 --- a/docs/hipchat +++ b/docs/hipchat @@ -1,25 +1,27 @@ -HipChat -======= +Note: Atlassian provides its own native integration for GitHub. We recommend +using the Atlassian integration, as opposed to this GitHub provided service. +Read more here: https://hipchat.com/addons/github-for-hipchat -Note: This service currently enables all of the available hook events including +This service currently enables all of the available hook events including commit comments, downloads, forks, fork applies, wiki updates, issues, issue comments, member adds, pull requests, pushes, and watches. A full list with -descriptions can be found here: http://developer.github.com/v3/repos/hooks/ +descriptions can be found here: https://developer.github.com/v3/repos/hooks/ Install Notes ------------- - 1. Auth token - One of your group's API tokens. See: http://www.hipchat.com/docs/api/auth - 2. Room - The full name of the room to send the message to. The room's room_id will also work. - 3. Notify - Whether or not to notify room members. +1. **Auth Token** - One of your group's API tokens. See: http://www.hipchat.com/docs/api/auth +2. **Room** - The full name of the room to send the message to. The room's room_id will also work. +3. **Restrict to Branch** - List of branches to which will be inspected. +4. **Color** - Optional background color for the message. Available options are: yellow, red, green, purple, gray, or random. Defaults to yellow. +5. **Server** - If you use a self-hosted HipChat server, enter the name of your server here, e.g. chat.example.com. Defaults to api.hipchat.com. -Developer Notes ---------------- - -data - - auth_token - - room - - notify (boolean) - -payload - - refer to docs/github_payload +Options +1. **Notify** - Whether or not to notify room members. +2. **Quiet Fork** - Suppress the room notice if a project has been forked. +3. **Quiet Watch** - Suppress the room notice that someone has started to watch the project. +4. **Quiet Comments** - Suppress the room notice if a comment has been made on repo. +5. **Quiet Labels** - Suppress the room notice if an issue has had any label changes. +6. **Quiet Assigning** - Suppress the room notice if an issue has been assigned or unassigned. +7. **Quiet Wiki** - Suppress the room notice if a wiki item has been updated. +8. **Active** - Turn On or Off the service hook. diff --git a/docs/hostedgraphite b/docs/hostedgraphite new file mode 100644 index 000000000..a05bc4478 --- /dev/null +++ b/docs/hostedgraphite @@ -0,0 +1,10 @@ +Hosted Graphite is a time-series database for application and performance monitoring. +Integrating it with GitHub gives you a realtime view of the commits happening on your codebase. +See http://www.hostedgraphite.com/ for more information. + +Install Notes +------------- + +1. API Key - Add your API key from your Hosted Graphite account page, located at https://www.hostedgraphite.com/accounts/profile/ +2. Bingo! GitHub will send details of each commit to your Hosted Graphite account. + diff --git a/docs/huboard b/docs/huboard new file mode 100644 index 000000000..7b87f86a5 --- /dev/null +++ b/docs/huboard @@ -0,0 +1,9 @@ +Instant project management for you GitHub repositories + +See: https://huboard.com + +HuBoard is built from the ground up using the GitHub public API. HuBoard issues **are** GitHub issues, you will never +have to deal with synchronization problems. Keep you issues where they belong, in the repository with you code. + +By enabling GitHub's Service Hook integration, HuBoard keeps your agile boards up to date by sending events that occur +on GitHub directly to HuBoard. diff --git a/docs/humbug b/docs/humbug new file mode 100644 index 000000000..7af7b0f03 --- /dev/null +++ b/docs/humbug @@ -0,0 +1,24 @@ +Install Notes +------------- + +Zulip is a group communication tool. + +https://zulip.com/hello + +Configuration Fields +-------------------- + +1. **Email** - The user to authenticate as. +2. **Api Key** - The API key associated with the provided user. See: https://zulip.com/#settings +3. **Stream** - The default Zulip stream where event notifications will be sent. Defaults to "commits" if left empty. The stream (or the "commits" stream if empty) must exist in Zulip. +4. **Commit Stream** - Overrides **Stream** for Git commit notifications. The stream must exist in Zulip. +5. **Issue Stream** - Overrides **Stream** for GitHub issues notifications. The stream must exist in Zulip. +6. **Branches** - A comma-delimited whitelist of branches to receive event notifications about (e.g., "master,staging,prod"). If empty, event notifications will be sent about all branches. +7. **Alternative Endpoint** - Specify the full URL for the Zulip server's GitHub endpoint (e.g., https://zulip.example.com/api/v1/external/github). If empty, the integration will talk to zulip.com. + +Checkboxes +---------- + +1. **Exclude Pull Requests** - Don't send GitHub pull request notifications to Zulip. +2. **Exclude Issues** - Don't send GitHub issues notifications to Zulip. +3. **Exclude Commits** - Don't send Git commit notifcations to Zulip. diff --git a/docs/ibmdevopsservices b/docs/ibmdevopsservices new file mode 100644 index 000000000..cdd20c818 --- /dev/null +++ b/docs/ibmdevopsservices @@ -0,0 +1,18 @@ +This service replaces the Rational JazzHub service to integrate GitHub with IBM Bluemix DevOps Services (http://hub.jazz.net). The hook automatically adds change set links in the work item specified by the commit message. + +The hook will recognize any of the common work item type names in the commit message and look for a corresponding work item. + +For example: + - "Fix bug 42" + - "Deliver Story 99" + - "[work item 999] fixed some stuff" + +Install Notes +------------- + +The IBM Bluemix DevOps Services hook needs to be configured with your DevOps Services login info: + +1. Ibm - The user ID that is used to access DevOps Services. +2. Ibm password - The password for the user ID that is used to access DevOps Services. If your organization uses federated authentication, you must use a personal access token. See [Setting up the GitHub hook](https://hub.jazz.net/docs/githubhooks/#github_hook) for details. +3. Project membership - The configured user needs to be a member of the DevOps Services project. +4. Override server url - The DevOps Services server url (for IBM internal testing only). diff --git a/docs/icescrum b/docs/icescrum new file mode 100644 index 000000000..8cbfb36d9 --- /dev/null +++ b/docs/icescrum @@ -0,0 +1,20 @@ +iceScrum is a an open source web application for using Scrum while keeping the spirit of a collaborative workspace. It also offers virtual boards with post-its for sprint backlog, product backlog and others. You can use iceScrum on your own server or in the cloud (SaaS) with Kagilum. + +Integrating with GitHub will allow you to associate Git commits to specific tasks. If you add a task number (ID) to your commit message, iceScrum Pro will associate the commit with the task. + +For example, if you add the text 'T76' to your commit message, iceScrum Pro will attach the commit to task 76 and move the task to in Progress column in the Sprint Plan. One or more TXX in the same commit message. You can also update the remaining time on the task if you write : 'T76-4' : iceScrum will update the remaining time to 4 hours for the task 76. + +This allows your team to stay up to date on the latest development work and view a history of commits for each story / task. + +More information on iceScrum : http://www.icescrum.org +More information on iceScrum : https://www.kagilum.com/icescrum-pro/ +More information on Kagilum : https://www.kagilum.com + +Install Notes +------------- + +1. **project_key** - This is your iceScrum Project key. +2. **username** - Username of one team member in the project (such as the ScrumMaster). +3. **password** - Password of one team member in the project. +4. **base_url** - Base_url is the url where your iceScrum is alive (http://www.example.com/icescrum). This input is optional. If not set, "https://www.kagilum.com/a" will be used for iceScrum Cloud customers. + diff --git a/docs/irc b/docs/irc index 5acfcc32c..a3773eefd 100644 --- a/docs/irc +++ b/docs/irc @@ -1,39 +1,125 @@ -IRC -======== - -Install Notes -------------- - -1. `server` (ie. irc.freenode.net) -2. `port` is normally 6667 / 9999 for ssl -3. the `room` field can support multiple rooms (comma separated) -4. the `password` field is for the server password -5. room passwords can be specified for each room like this: - room_name::password -6. prefixing '#' to the room field is optional -7. `nick` is not required. If provided, the IRCBot will use that - nick. -8. Enable `long_url` so that compare/commit url's are not passed - through bit.ly. -9. Enable `message_without_join' to keep the GitHub IRC bot from - joining and immediately leaving the channel for each push. -10. Enable `no_colors` to disable colors in the messages sent by the - GitHub IRC bot. - - -Developer Notes +Example +------- + +For [freenode](https://freenode.net/): + +- Server: `chat.freenode.net` +- Room: `#CHANNELNAME` +- Message without join: _yes_ +- Notice: _yes_ +- Active: _yes_ + +Remember to allow [external messages](#message-without-join) and [colors](#no-colors) in the channel: + +``` +/msg ChanServ set #CHANNELNAME mlock -nc +``` + + +Usage +----- + +### Server + +IRC server address. + +> For freenode, use `chat.freenode.net` + + +### Port + +Port to use when connecting to the IRC server (optional). + +Default ports are `6667` (no SSL), and `6697` (SSL). + +> [freenode servers](https://freenode.net/irc_servers.shtml) listen on `6665`, `6666`, `6667`, `8000`, `8001`, and `8002` (no SSL and SSL), and `6697`, `7000`, and `7070` (SSL only). + + +### Room + +IRC channel (or channels) to which messages will be sent. + +Supports single or multiple channels (comma separated). Also supports channel passwords (`CHANNELNAME::PASSWORD`). Prefixing the channel name with `#` is optional. + + +### Nick + +Nickname to use when sending messages (optional). + + +### Branches + +Names of the git branches (or refs) to monitor for events (comma-separated). + + +### Nickserv password + +Password for the IRC server’s [NickServ](http://en.wikipedia.org/wiki/Internet_Relay_Chat_services) (optional). + +> For freenode, see the [nickname setup instructions](https://freenode.net/faq.shtml#nicksetup). + + +### Password + +Password for the IRC server (optional). + + +### Ssl + +Enables connecting to the IRC server via SSL. + + +### Message without join + +Enables sending messages to the channel without joining the channel first. + +_Recommended_, as it decreases channel noise. Requires the IRC channel to allow external messages. + +> On freenode, allow external messages to the channel by messaging [ChanServ](https://freenode.net/services.shtml): +> +> ``` +> /msg ChanServ set #CHANNELNAME mlock -n +> ``` +> +> On most other IRC networks, set the channel mode directly: +> +> ``` +> /mode #CHANNELNAME -n +> ``` + + +### No colors + +Disables colors in messages. + +_Enabling_ colors in messages may require the IRC channel to allow colors. + +> On freenode, allow colors in the channel by messaging ChanServ: +> +> ``` +> /msg ChanServ set #CHANNELNAME mlock -c +> ``` + + +### Long url + +Enables including full URLs in messages. When disabled, [git.io](https://git.io/) URLs will be used. + + +### Notice + +Enables sending IRC notices instead of regular messages. + +_Recommended_, as it decreases channel noise. + + +Troubleshooting --------------- -data - - server - - port - - room - - password - - ssl - - nick - - long_url - - message_without_join - - no_colors - -payload - - refer to docs/github_payload +Diagnostic information can be retrieved from GitHub with an HTTP request. + +Use your GitHub `USERNAME` and `PASSWORD` to authenticate, and specify the appropriate repository with `REPOUSER` and `REPONAME`. + +``` +curl -i -u 'USERNAME:PASSWORD' https://api.github.com/repos/REPOUSER/REPONAME/hooks +``` diff --git a/docs/irker b/docs/irker new file mode 100644 index 000000000..ec065a943 --- /dev/null +++ b/docs/irker @@ -0,0 +1,9 @@ +1. `address` is the irker service address. +2. `channels` is the channel or list of channels the commit notification should be posted to. Format is irc://chat.freenode.net/#commits. Multiple urls should be semicolon (;) separated. +3. `project` is the optional project name, defaults to the Repository name. +4. `branch` lets you specify the branch name, or a template with `%s` to fill the branch name in. +5. `long_urls` enables full URLs instead of shortened URLs. +6. `module` sets the module name in the project. +7. `color` adds mIRC color codes to the message. +8. `full_commits` sends multiple messages to show up to 5 lines of the commit message. + diff --git a/docs/ironmq b/docs/ironmq new file mode 100644 index 000000000..5b866bb91 --- /dev/null +++ b/docs/ironmq @@ -0,0 +1,11 @@ +Posts all of your GitHub events to IronMQ so you can process the events asynchronously. This can also be used as a safe +buffer between GitHub and your application so you never miss a GitHub event. + +For more information about IronMQ, see [www.iron.io](http://www.iron.io). + +Install Notes +------------- + +1. **token** - Your Iron.io OAuth token. Get yours at: http://hud.iron.io/tokens +2. **project_id** - The Iron.io project ID you you'd like to use. +3. **queue_name** - The name of the queue to post to. diff --git a/docs/ironworker b/docs/ironworker new file mode 100644 index 000000000..8d9a57fbb --- /dev/null +++ b/docs/ironworker @@ -0,0 +1,10 @@ +Posts all of your GitHub events to IronWorker so you can write IronWorker scripts for testing and deploying applications. + +For more information about IronWorker, see [www.iron.io](http://www.iron.io). + +Install Notes +------------- + +1. **token** - Your Iron.io OAuth token. Get yours at: http://hud.iron.io/tokens +2. **project_id** - The Iron.io project ID you you'd like to use. +3. **code_name** - The name of the worker code to post to. diff --git a/docs/jabber b/docs/jabber index 8d084eef1..a56ec9cb2 100644 --- a/docs/jabber +++ b/docs/jabber @@ -1,18 +1,5 @@ -Jabber -====== - Install Notes ------------- - 1. user is the Jabber ID (e.g.: myusername@jabber.org). Multiple users can be added by separating them with commas. Currently, there is a maximum of 25 users. - 2. muc is the Jabber ID (e.g.: talks@conference.jabber.org/Nick). Multiple conferences can be added by separating them with commas. - -Developer Notes ---------------- - -data - - user - - muc - -payload - - refer to docs/github_payload + 1. **User** is the Jabber ID (e.g.: myusername@jabber.org). Multiple users can be added by separating them with commas. Currently, there is a maximum of 25 users. Add `github-services@jabber.org` to receive notifications. + diff --git a/docs/jaconda b/docs/jaconda index 7292c37e6..d479e715a 100644 --- a/docs/jaconda +++ b/docs/jaconda @@ -1,25 +1,12 @@ -Jaconda -======== - Install Notes ------------- +Pushes all Hooks for this Repository to Jaconda (Pushes, Issues, Pull Requests, etc). + Using the following url as an example: https://bigbang.jaconda.im/rooms/galaxo - 1. Subdomain is bigbang - 2. Room ID is galaxo - 3. Room token can be found on the Room Integration page - 4. if Digest is checked, all commits will be compressed in one message - -Developer Notes ---------------- - -data - - subdomain - - room_id - - room_token - - digest (boolean) - -payload - - refer to docs/github_payload \ No newline at end of file +1. Subdomain is bigbang +2. Room ID is galaxo +3. Room token can be found on the Room Integration page +4. if Digest is checked, all commits will be compressed in one message diff --git a/docs/jenkins b/docs/jenkins new file mode 100644 index 000000000..56487398a --- /dev/null +++ b/docs/jenkins @@ -0,0 +1,12 @@ +[Jenkins](http://jenkins-ci.org/) is a popular continuous integration server. + +Using the Jenkins GitHub Plugin you can automatically trigger build jobs when +pushes are made to GitHub. + +Install Notes +------------- + + 1. "Jenkins Hook Url" is the URL of your Jenkins server's webhook endpoint. For + example: `http://ci.jenkins-ci.org/github-webhook/`. + +For more information see . diff --git a/docs/jenkinsgit b/docs/jenkinsgit new file mode 100644 index 000000000..0d351a3d8 --- /dev/null +++ b/docs/jenkinsgit @@ -0,0 +1,28 @@ +Install Notes +------------- + +* Requires **[Git Plugin][wiki] v1.1.18**, released 2012-04-27, and the "Poll SCM" + build trigger needs to be enabled. (Though you can have it poll very + infrequently, I recommend something like `0 */3 * * *`) +* "Jenkins Url" is the base URL of your [Jenkins][] server. For example: + `http://ci.jenkins-ci.org/`. We will hit `/git/notifyCommit` under this URL. (See + [the Git plugin wiki page][wiki-push] for more details.) + +Details +------- + +[Jenkins][] is a popular continuous integration server. + +If you're using the standard [Jenkins Git plugin][wiki] to poll & check out +your repository, you can quickly and easily switch to a push model using this +service. + +It will send a request to your Jenkins instance telling it about the +repositories and branches that changed. Jenkins will then poll the repository +and build if needed. See [push notification from repository][wiki-push] on the +Jenkins wiki for information. + +[jenkins]: http://jenkins-ci.org/ "Jenkins CI Server" +[wiki]: https://wiki.jenkins-ci.org/display/JENKINS/Git+plugin +[wiki-push]: https://wiki.jenkins-ci.org/display/JENKINS/Git+plugin#GitPlugin-Pushnotificationfromrepository + diff --git a/docs/jira b/docs/jira index 7062c53b1..1ff7b7d4d 100644 --- a/docs/jira +++ b/docs/jira @@ -1,11 +1,8 @@ -Jira -======== - -This service hook allows you to transition Jira tickets using the REST API available in -version 4.2+ of Jira. To interact with tickets in Jira you will need to place markup +This service hook allows you to transition JIRA tickets using the REST API available in +version 4.2+ of JIRA. To interact with tickets in JIRA you will need to place markup similar to Lighthouse's in your commit message. - Fixed an annoying bug [#WEB-210 transition:31 resolution:1] + Fixed an annoying bug [#WEB-210 transition:31 resolution:1] This will perform transition 31 with a resolution code of 1 on the issue WEB-210. You can specify any key value pair with the `key:value` notation, but at the very least transition must be present. @@ -15,26 +12,15 @@ versa. Install Notes ------------- -A user in Jira will need to be created for GitHub. It should be given full + +A user in JIRA will need to be created for GitHub. It should be given full access to all projects. Using the following url as an example: -http://jira.enmasse.com/rest/2.0.alpha1/issue/WEB-249 - - 1. http://jira.enmasse.com is the server_hostname - 2. 2.0.alpha1 is the api_version - 3. username of the GitHub user in Jira - 4. password of the GitHub user in Jira - - -Developer Notes ---------------- -data - - server_url - - api_version - - username - - password + http://jira.enmasse.com/rest/2.0.alpha1/issue/WEB-249 -payload - - refer to docs/github_payload +1. http://jira.enmasse.com is the server_hostname +2. 2.0.alpha1 is the api_version +3. username of the GitHub user in JIRA +4. password of the GitHub user in JIRA diff --git a/docs/kanbanery b/docs/kanbanery index d236ec05f..f97deacb6 100644 --- a/docs/kanbanery +++ b/docs/kanbanery @@ -1,18 +1,5 @@ -Kanbanery -====== - Install Notes ------------- - 1. project token - Your project GitHub token from Kanbanery - 2. project id - -Developer Notes ---------------- - -data - - project_token - - project_id - -payload - - refer to docs/github_payload \ No newline at end of file +1. project token - Your project GitHub token from Kanbanery +2. project id diff --git a/docs/kanbanize b/docs/kanbanize new file mode 100644 index 000000000..2b3a2b963 --- /dev/null +++ b/docs/kanbanize @@ -0,0 +1,9 @@ +Install Notes +------------- + +1. **Kanbanize domain name** - Your project Kanbanize domain name Ex: **mycompany.kanbanize.com** +2. **Kanbanize api key** - The API key of a Kanbanize user account. We recommend using a dedicated Administrator user or a standard user (e.g. GitHub_user) with permissions to access, comment and modify cards on the desired boards. +3. **Branch Filter** - A Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. +4. **Restrict to Last Commit** - Only the last commit of each push will be inspected for task IDs. +5. **Track Project Issues In Kanbanize** - Open/Move to Done a card when a project issue is Open/Closed +6. **Project Issues Board Id** - The ID of the board that must keep the project issue cards. You can see the board ID on the Kanbanize dashboard screen, next to the board name. diff --git a/docs/kickoff b/docs/kickoff deleted file mode 100644 index bbd1fdb00..000000000 --- a/docs/kickoff +++ /dev/null @@ -1,18 +0,0 @@ -Kickoff -======== - -Install Notes -------------- - - 1. the project id and project token are available in the Kickoff app by right clicking on a project or through the API - - -Developer Notes ---------------- - -data - - project_id - - project_token - -payload - - refer to docs/github_payload diff --git a/docs/landscape b/docs/landscape new file mode 100644 index 000000000..8ec335c0f --- /dev/null +++ b/docs/landscape @@ -0,0 +1,15 @@ +Landscape is a continuous code quality inspector for Python projects. + +By enabling this hook, Landscape will listen for pushes and on each push, instigate a new code check. + +Install Notes +------------- + +Landscape will automatically install this hook, there is no need to do it manually. + +To install: + +1. Create an account at https://landscape.io (you can sign in with GitHub) +2. Enable checking on a repository. The hook will be automatically installed. + +For more information about Landscape, see https://docs.landscape.io/ diff --git a/docs/leanpub b/docs/leanpub new file mode 100644 index 000000000..a0744520f --- /dev/null +++ b/docs/leanpub @@ -0,0 +1,9 @@ +This service starts a preview of your book on Leanpub whenever a push is made to your repository. + +Required values are: + +api_key: Your Leanpub api_key, which you can find at https://leanpub.com/author_dashboard/settings. + +slug: The slug for your book. This is the part of the URL for your book after https://leanpub.com/. For example, for https://leanpub.com/lean, the slug is lean. + +See https://leanpub.com/help/api for more information on the Leanpub API. diff --git a/docs/leanto b/docs/leanto deleted file mode 100644 index 0ab0c7560..000000000 --- a/docs/leanto +++ /dev/null @@ -1,35 +0,0 @@ -Lean-to -======= - -The current version of Lean-To adds the ability to automatically link specific commits at GitHub with stories and bugs in your Lean-To projects. This feature is currently experimental and is only enabled for internal Refactr projects. If you would like access to this experimental feature please email support@lean-to.com. - -Using the integration is as simple as tagging a Lean-To story or bug in your commit message: - - This is my commit message. I fixed a bug. - [Bug:7] - -When you push to GitHub it passes a list of commits to Lean-To which scans the commit messages for tags like `[Bug:7]`. If tags are found, Lean-To will automatically add a note to the card with the commit message and a link back to the actual commit. The tags may also contain a directive to change certain properties of the card: - - Added feature foo. [Story:17, status:completed] - -In this example, Lean-To would add a note to the story and mark it as completed. Lean-To also tries to match your GitHub account to a Lean-To account and will mark you as the owner of the story if you change the status of a story and it doesn't already have an owner. - -GitHub integration is currently enabled for the **Milemarker**, **Lean-To**, **Realief**, and **Qualtrx** projects for internal testing. - -### Tag Syntax -* Tags must start and end with square brackets, e.g. `[Story:1]` -* Tags are case-insensitive so `[story:1]` is equivalent to `[Story:1]` -* There must be a colon between 'Story' or 'Bug' and the number so `[Story:1]` is valid but `[bug7]` is not -* Property directives are optional. They must be separated by a comma, and they must follow the card identifier, e.g. `[Bug:1, status:completed, actual:1]` -* Valid status values are: '**not** started', '**in** progress', '**complete**d' -* Only the bold portion of the status value is required so `[Bug:1, status:in progress]` is equivalent to `[Bug:1, status:in]` -* Multiple tags may be specified in a single commit message so `[Story:3] [Bug:1, status:complete]` will add notes to both Story 3 and Bug 1 -* Acceptable card properties are: 'status', 'priority', 'iteration', 'estimate', and 'actual' -* Card properties do not need to be in any order as long as they follow the card identifier. So `[Story:1, iteration:2, estimate:1/2]` is valid, but `[status:completed, Story:1]` is not. -* Acceptable estimates are '0', '1/4', '0.25', '1/2', '0.5', '1', '2', '3', '5', '8', '13', '20', 40', and '80'. '0' will clear the estimate, and the rest will translate to their equivalent Lean-to estimate values. - - -Install Notes -------------- - - 1. Token - GitHub Service Hook Token for your Lean-to project. Get it by clicking "My Account" > "Projects & People" > "Enable API". diff --git a/docs/lighthouse b/docs/lighthouse index 1600498ce..76f839f05 100644 --- a/docs/lighthouse +++ b/docs/lighthouse @@ -1,29 +1,15 @@ -Lighthouse -======== - Install Notes ------------- Using the following url as an example: http://logicalawesome.lighthouseapp.com/projects/8570-github/overview - 1. subdomain is logicalawesome - 2. project_id is 8570 - 3. this service only supports project tokens + 1. **Subdomain** is logicalawesome + 2. **Project Id** is 8570 + 3. This service only supports project tokens (accepting patches for user-based tokens) - 4. 'private' determines whether changed files - should be displayed (useful for public LH + 4. **Private** determines whether changed files + should be displayed (useful for public LH accounts and private GH repos) - - -Developer Notes ---------------- - -data - - subdomain - - project_id - - token - - private - -payload - - refer to docs/github_payload + 5. **Send Only Ticket Commits** ensures that the only commits sent to + Lighthouse are ones with Lighthouse ticket codes. diff --git a/docs/lingohub b/docs/lingohub new file mode 100644 index 000000000..d29104c3f --- /dev/null +++ b/docs/lingohub @@ -0,0 +1,8 @@ +Start localizing your software to increase market share and improve customer satisfaction. +Localization is simple with lingohub. Be Global, Go Local! + +Install Notes +------------- + +1. Please enter your Project Token found in your project settings page. +2. Now edit your 'Watch Patterns on the 'Imports/Edit your repository settings' page. Only files that contain translations for your master locale should be included in the given regular expression. diff --git a/docs/loggly b/docs/loggly deleted file mode 100644 index 68f0f8265..000000000 --- a/docs/loggly +++ /dev/null @@ -1,19 +0,0 @@ -Loggly -======== - -This service hook allows you to log pushed commits directly to a Loggly HTTP -input. - -Install Notes -------------- - - 1. input token -- take this from the detail page for your HTTP input. - -Developer Notes ---------------- - -data - - input_token - -payload - - refer to docs/github_payload diff --git a/docs/mantis_bt b/docs/mantisbt old mode 100755 new mode 100644 similarity index 59% rename from docs/mantis_bt rename to docs/mantisbt index 5b667fd98..5b3f6454e --- a/docs/mantis_bt +++ b/docs/mantisbt @@ -1,22 +1,7 @@ -MantisBT -======== - Install Notes ------------- Requires the Source Integration plugins from http://leetcode.net/projects/source-integration/ 1. Base URL to MantisBT install - 2. Secret API Key set at http://yoursite/mantis/plugin.php?page=Source/manage_config_page - - -Developer Notes ---------------- - -data - - url - - api_key - -payload - - refer to docs/github_payload - + 2. Secret API Key set at http://yoursite/mantis/plugin.php?page=Source/manage_config_page \ No newline at end of file diff --git a/docs/masterbranch b/docs/masterbranch deleted file mode 100644 index 269b40457..000000000 --- a/docs/masterbranch +++ /dev/null @@ -1,16 +0,0 @@ -Masterbranch -======== - -Install Notes -------------- - -With this service your commit's logs will be sent to Masterbranch.com . Masterbranch will proportionate you with an automatic-updated profile of your project. This profile will show the different technologies you are using, including some stats based on the contributors development. - -Developer Notes ---------------- - -data - - none - -payload - - refer to docs/github_payload diff --git a/docs/maxcdn b/docs/maxcdn new file mode 100644 index 000000000..964b3c5d8 --- /dev/null +++ b/docs/maxcdn @@ -0,0 +1,16 @@ +Purge your MaxCDN Pull Zone +--------------------------- + +**Setup Notes** + + 1. Get your "Company Alias", "Consumer Key" and "Consumer Secret" from your [MaxCDN Account API Page][account_api_url]. + 2. Get your "Zone ID" from your [MaxCDN Account Pull Zones Overview page][account_pull_zone]. + 3. "Static Only" will only purge your zone if modified files include static files — `css`, `js`, `jpg`, `jpeg`, `gif`, `ico`, `png`, `bmp`, `pict`, `csv`, `doc`, `pdf`, `pls`, `ppt`, `tif`, `tiff`, `eps`, `ejs`, `swf`, `midi`, `mid`, `txt`, `ttf`, `eot`, `woff`, `otf`, `svg`, `svgz`, `webp`, `docx`, `xlsx`, `xls`, `pptx`, `ps`, `rss`, `class`, `jar`. + 4. Whitelist the GitHub web hook IP block with MaxCDN. For instructions, see MaxCDN's support page on [How To Whitelist Your Server IP To Use The API][whitelist_article]. + +> GitHub's IPs may change from time to time, if you're having issues, verify that the IP you've whitelisted is still current, by checking the "hook" key at [GitHub's "meta" API endpoint][meta_endpoint]. + +[account_api_url]: https://cp.maxcdn.com/account/api +[account_pull_zone]: https://cp.maxcdn.com/zones/pull +[whitelist_article]: http://support.maxcdn.com/tutorials/how-to-whitelist-your-server-ip-to-use-the-api/ +[meta_endpoint]: https://api.github.com/meta diff --git a/docs/mqttpub b/docs/mqttpub new file mode 100644 index 000000000..8d927b2f8 --- /dev/null +++ b/docs/mqttpub @@ -0,0 +1,16 @@ +This service lets you publish push and commit messages to a message broker via the MQTT protocol. + +Install Notes +------------- + +1. OPTIONAL. 'broker' is the hostname of the MQTT broker (default is q.m2m.io). +2. OPTIONAL. 'port' is the port to connect (default is 1883, MQTT standard). +3. 'topic' is the name of the topic to publish on. Suggested example: 'github/{github_username}/{repo_name} . +4. OPTIONAL. 'clientid' is the unique client ID which publishes the payload message (default is a prefixed epoc time long, e.g. "github_1336363787"). +5. OPTIONAL. 'user' is the v3.1 username (default is none). +6. OPTIONAL. 'pass' is the v3.1 password (default is none). +7. OPTIONAL. 'retain' specifies whether or not the publication should be retained as the last/most recent message for new subscribers. +8. The payload of the message is JSON received from GitHub (refer to docs/github_payload). +9. ruby-mqtt only supports QoS 0. +10. For testing, you can use mqtt.io as your web MQTT client. broker: q.m2m.io, port: 1883, clientid: {github_username}. You will "Subscribe to Topics": 'github/{github_username}/{repo_name}'. Unauthenticated username/password sets default to the public channel! + diff --git a/docs/myget b/docs/myget new file mode 100644 index 000000000..8407e9215 --- /dev/null +++ b/docs/myget @@ -0,0 +1,19 @@ +MyGet allows you to create and host your own NuGet feed. Include packages from +the official NuGet feed or upload your own NuGet packages. We can also compile +and package your source code from GitHub, BitBucket, CodePlex and more! + +The GitHub MyGet service can be used to trigger builds after changes have been +pushed to your GitHub repository. + +Install Notes +------------- + +1. Login to https://www.myget.org. If you are on the Enterprise plan, + login through that URL instead. + +2. Navigate to the feed's build services setup for which you want to + automatically trigger builds when changes have been pushed to GitHub. + +3. For the build, copy the Hook (HTTP POST) URL into the Hook URL field. + +4. Show your love for MyGet and include the Status badge in your README.md diff --git a/docs/notifo b/docs/notifo deleted file mode 100644 index 2fb69bd3c..000000000 --- a/docs/notifo +++ /dev/null @@ -1,23 +0,0 @@ -Notifo -========== - -This service lets your broadcast your commits on Notifo, a mobile notifications platform. Get commit notifications on your iPhone and other mobile devices immediately via PUSH. Notifo also OS X, Linux and Windows notification apps. If multiple commits are in a push you will be shown the last with a message stating how many more exist, and a link to the compare url. - -Install Notes -------------- - - 1. Create a user account on Notifo at https://notifo.com/register - 2. Type in your username and the usernames of others wanting to be notified of commits to this repository with Notifo into the subscribers field. - 3. Enjoy. - -Notifications are only sent after the service (GitHub) has been approved to send you notifications on Notifo. This happens after the first commit after this service is enabled. You will begin receiving commit notifications from then on. - - -Developer Notes ---------------- - -data - - subscribers (csv list of up to 25 Notifo usernames) - -payload - - refer to docs/github_payload diff --git a/docs/notifymyandroid b/docs/notifymyandroid deleted file mode 100644 index 93560f652..000000000 --- a/docs/notifymyandroid +++ /dev/null @@ -1,23 +0,0 @@ -Notify My Android -========== - -With NotifyMyAndroid (NMA) you can push notifications to your Android devices -through Google's C2DM notification system. The [public Web API](http://www.notifymyandroid.com/api.php) -can be used by virtually any application to delivery push notifications to -your Android. - -Install Notes -------------- - -1. Register at https://www.notifymyandroid.com/register.php. - -2. Generate an API key at https://www.notifymyandroid.com/account.php to be used on GitHub. - -Developer Notes ---------------- - -apikey - - Your NMA API Key, or a CSV list of API Keys. - -payload - - refer to docs/github_payload diff --git a/docs/obs b/docs/obs new file mode 100644 index 000000000..7cbe7be0d --- /dev/null +++ b/docs/obs @@ -0,0 +1,45 @@ +# Open Build Service + +[Open Build Service (OBS)][obs] is a Web service for building packages for various Linux distributions. It's run by OpenSUSE project but available for any open-source community. + +This hook enables you to trigger your OBS build once new commits are pushed into the repository. It is configured to use *api.opensuse.org* host by default, but you may use it for your own OBS instance as well. It must be version 2.5 or higher, though. + +[obs]: http://build.opensuse.org + +## Prerequisites + +1. Configure your OBS project to fetch sources from Git. You would have to add a *Source Service* for this to work, check OBS manual for more information. + +2. Create an authentification token for your account using `osc` command line tool. + For a generic token not tied to the specific package: + + # osc token --create + + For a token which can be used only for specific package + + # osc token --create PROJECT PACKAGE + + In the former case you may use single token in multiple Web hooks, to trigger multiple projects, but you would have to specify those OBS project and repository names. If the latter case you have to use different tokens for different projects, but you may leave "Project" and "Package" fields blank here, simply using the token only is enough. + + To see all your already-created tokens, run + + # osc token + + You may also use a comma-separated list of tokens to trigger several OBS builds in order. + +## Parameters + + - `Url` + This is the URL where OBS instance resides. You should alter this parameter only if you're using a dedicated OBS instance, leave it empty if you just use OpenSUSE's server. + + - `Project` + The package fully-qualified name on the instance. This should be something like `devel:languages:haskell`, where `devel:languages` is a namespace part of name. You may leave this field blank if your token is project-specific. + + - `Package` + The package name within the project. This should be something like `ghc` for the example above. You may also leave this field blank if your token is project-specific. + + - `Token` + The comma-separated list of tokens to be used for triggering builds. This is the only required field here. See the "Prerequisites" section for more information on token creation. + + - `Refs` + The filter expression for pushed referenced. This field lists colon-separated patterns, build is triggered only if at least one of them satisfies. You may leave it blank, then no filtering is done by default. But you should consider setting this field to something like `refs/heads/master` or `refs/tags/version-*` in order not to rebuild your packages on each commit. diff --git a/docs/ontime b/docs/ontime index 0bf3ef49f..afa7c0900 100644 --- a/docs/ontime +++ b/docs/ontime @@ -1,14 +1,14 @@ -OnTime GitHub Integration -========================= - Install Notes ------------- In order to properly configure OnTime and GitHub to work together a few settings have to be configured. 1. Log into your OnTime v11.1+ installation as the Administrator or with a user that has System Options privledges. + 2. Open up the "Tools" menu and click on "System Options" and then go to the "GitHub Integration" option in the left pane. + 3. Make sure that "Enable GitHub Integration" is checked and that an API Key exists. If it doesn't then click "Generate a New API Key" to make one + 4. Copy the API key and paste that into the OnTime service hook in your GitHub repository. NOTE: If you have a non-hosted installation of OnTime your installation must have access to the internet. Hosted accounts already have internet access. @@ -43,10 +43,3 @@ A few examples of valid tags (assuming you have hours as a timeunit in OnTime an [oti: srx0091] -Developer Notes ---------------- - -data - - ontime_url - - api_key - diff --git a/docs/pachube b/docs/pachube deleted file mode 100644 index 1b5cf6c87..000000000 --- a/docs/pachube +++ /dev/null @@ -1,23 +0,0 @@ -Pachube -======= - -Publishes number of distinct commits per push to a [Pachube](https://pachube.com/) feed. -Datastreams are created / updated for each repository name. - -Install Notes -------------- - - 1. Create a feed on [Pachube](https://pachube.com/). - 2. Create an api key that has PUT permissions on the feed. - - -Developer Notes ---------------- - -data - - api_key - - feed_id - -payload - - refer to docs/github_payload - diff --git a/docs/packagist b/docs/packagist new file mode 100644 index 000000000..d217794ed --- /dev/null +++ b/docs/packagist @@ -0,0 +1,16 @@ +Packagist – the main Composer repository + +Install Notes +------------- + + 1. Create an account on packagist.org + 2. Enter your credentials + - The token which you can find on the Packagist profile page + + Optional steps: + - Enter the username (**not** email) who the API token belongs to (defaults to the repository owner) + - Enter the host of your Packagist installation (defaults to https://packagist.org), the protocol-prefix is optional and defaults to "http://". + + 3. Check the "Active" checkbox and click "Update Service". + +For more details about Packagist, visit https://packagist.org/ diff --git a/docs/phraseapp b/docs/phraseapp new file mode 100644 index 000000000..bf9870bec --- /dev/null +++ b/docs/phraseapp @@ -0,0 +1,10 @@ +*Your companion to become a global player. The translation management software for websites and mobile apps.* + +This service lets you connect your GitHub repository to an existing [PhraseApp](https://phraseapp.com) translation project to automatically push new translations from your locale files into PhraseApp. + +Read more: [Documentation](https://phraseapp.com/docs) + +Install Notes +------------- + +Just add your project **auth token** to the hook configuration. You find your token on your project overview page after creating the first project in PhraseApp. diff --git a/docs/pivotal_tracker b/docs/pivotal_tracker deleted file mode 100644 index 0e6bb76d0..000000000 --- a/docs/pivotal_tracker +++ /dev/null @@ -1,18 +0,0 @@ -Pivotal Tracker -=============== - -Install Notes -------------- - - 1. token is your Pivotal Tracker API Token. This is at the bottom of your 'My Profile' page. - 2. branch is a space-separated list of the branches you want to listen for commits on. If none is provided it will listen on all branches. - -Developer Notes ---------------- - -data - - token - - branch - -payload - - refer to docs/github_payload diff --git a/docs/pivotaltracker b/docs/pivotaltracker new file mode 100644 index 000000000..676afbe61 --- /dev/null +++ b/docs/pivotaltracker @@ -0,0 +1,6 @@ +Install Notes +------------- + +1. "token" is your Pivotal Tracker API Token. This is at the bottom of your 'My Profile' page. +2. "branch" is a space-separated list of the branches you want to listen for commits on. If none is provided it will listen on all branches. +3. "endpoint" is an optional endpoint for a custom Pivotal Tracker installation. Leave this blank to use "https://www.pivotaltracker.com". diff --git a/docs/piwikplugins b/docs/piwikplugins new file mode 100644 index 000000000..a9042324a --- /dev/null +++ b/docs/piwikplugins @@ -0,0 +1 @@ +[Piwik](http://piwik.org) is an open analytics platform which can be extended with Plugins and Themes. This service notifies the [Piwik Marketplace](http://plugins.piwik.org) of any new releases to your Piwik plugin. See [http://developer.piwik.org/guides/distributing-your-plugin](the Publisher Guide) for more information. diff --git a/docs/planbox b/docs/planbox index 11cfb7458..3e527225e 100644 --- a/docs/planbox +++ b/docs/planbox @@ -1,12 +1,5 @@ -Planbox -======= +Install Notes +------------- -token is your Planbox Inititative Token. Find in on the Manage page. - -payload contains the commit data; same as you get when using API. See docs/github_payload. - -TEST ----- - -svc = Service::Planbox.new(:push, {'token' => '223cazxbs97de1ndj61d8aa435b0o9i05'}) -svc.receive_push + 1. The token is your Planbox Inititative Token. Find in on the Manage page. + 2. More information available on Planbox's help site. diff --git a/docs/planio b/docs/planio new file mode 100644 index 000000000..d63e1e3a0 --- /dev/null +++ b/docs/planio @@ -0,0 +1,15 @@ +Planio ♥ GitHub! This service hook notifies Planio to fetch new commits from your repository at GitHub whenever you push to it. + +Install Notes +------------- + +1. Create a free or paid Planio account at http://plan.io +2. Logged in to your Planio account, navigate to **Administration** -> **Settings** -> **Repositories** and make sure that the **Enable WS for repository management** checkbox is active and that you have an **API key** set up +3. Create a project, then navigate to **Settings** -> **Repositories** +4. Click on **New repository** +5. Select **Git**, choose an **Identifier**, enter the (preferably public, read-only, git://, non-HTTP) URL of your GitHub repository in the **Mirror from external URL** field, then click **Create** +6. Back in your project, head over to the **Repository** tab, find and select the repository you just created in the sidebar +7. Should you not see the blue instructions screen, click on **Help** on the upper right +8. Fill in the information from the **Set up web hook** section in the above form here on GitHub and click **Update settings** +9. Optional: if this GitHub repository is private, click on **Deploy Keys** here on the right and add the SSH public key from the **Set up a public key (deploy key)** section in Planio +10. A short while after your next commit, the repository will show up on Planio. If you have nothing to commit, you can also come back here and hit the **Test Hook** button. diff --git a/docs/presently b/docs/presently deleted file mode 100644 index f6075b09f..000000000 --- a/docs/presently +++ /dev/null @@ -1,21 +0,0 @@ -Present.ly -========== - -Install Notes -------------- - - 1. subdomain (for a Present.ly account) - 2. group_name (OPTIONAL; will broadcast as a group or as the system if group is 'system') - 3. username (user must be a manager of the specified group or admin for 'system') - -Developer Notes ---------------- - -data - - subdomain - - group_name (optional) - - username - - password - -payload - - refer to docs/github_payload diff --git a/docs/prowl b/docs/prowl index b9fe7667d..7837f59cb 100644 --- a/docs/prowl +++ b/docs/prowl @@ -1,6 +1,3 @@ -Prowl -========== - Prowl is an iPhone App+Web Service for arbitrary Push Notifications. After installing the app and registering the device, you receive an API key and can send notifications from your computer via Growl (with the Prowl GrowlStyle installed) or from any server with very little work (at least as far as the Push submission is concerned). To get started, create a user account for Prowl at https://www.prowlapp.com/. @@ -8,13 +5,4 @@ To get started, create a user account for Prowl at https://www.prowlapp.com/. Install Notes ------------- -1. Create a new API key for GitHub at the Prowl website https://www.prowlapp.com/api_settings.php. - -Developer Notes ---------------- - -apikey - - Your Prowl API key. - -payload - - refer to docs/github_payload +1. Create a new API key for GitHub at the [Prowl website](https://www.prowlapp.com/api_settings.php). diff --git a/docs/pushbullet b/docs/pushbullet new file mode 100644 index 000000000..f8e2b5465 --- /dev/null +++ b/docs/pushbullet @@ -0,0 +1,18 @@ +[Pushbullet](https://www.pushbullet.com/) makes getting things on and off your phone easy and fast. + +Supported events: push, issue and pull request. + +Install Notes +------------- + +1. Install the [Android client](https://play.google.com/store/apps/details?id=com.pushbullet.android) from the Android Market or a browser extension. + +2. Login to [Pushbullet](https://www.pushbullet.com/account) and copy your api key. + +3. Add your api key in the field above. + +If you want to send to a specific device, else leave the "Device iden" field blank: + +4. Open [Pushbullet API](https://api.pushbullet.com/api/devices) and paste your api key to the username field, leave password empty. + +5. Find your desired device from the output and copy it's iden to the field above. diff --git a/docs/pushover b/docs/pushover new file mode 100644 index 000000000..b86188c47 --- /dev/null +++ b/docs/pushover @@ -0,0 +1,11 @@ +Pushover is a cross-platform push notification service with a [simple API](https://pushover.net/api). + +Install Notes +------------- + +1. Install the [iOS client](https://pushover.net/clients) from the App Store, or the [Android client](https://pushover.net/clients) from the Android Market. + +2. Login to [Pushover](https://pushover.net/) and copy your user key. + +3. Add your user key in the field above. Optionally enter a device name to have notifications go to a single device, or leave blank to send to all active devices on your account. + diff --git a/docs/railsbp b/docs/railsbp deleted file mode 100644 index 4b703a4a1..000000000 --- a/docs/railsbp +++ /dev/null @@ -1,26 +0,0 @@ -Railsbp -======= - -Railsbp - a code analyzer service for rails projects. - -Install Notes -------------- - -1. Create an account on railsbp.com (just sign in with GitHub) -2. Create a repository on railsbp.com -3. Enter your token - - the token which you can find on repository edit page on railsbp.com -4. Enter your railsbp_url if you deploy the proxy on your own server -5. Check the "Active" checkbox and click "Update Settings" - -For more details about Railsbp, go to https://railsbp.com - -Developer Notes ---------------- - -data - - railsbp_url - - token - -payload - - refer to docs/github_payload diff --git a/docs/rally b/docs/rally new file mode 100644 index 000000000..c914db3d9 --- /dev/null +++ b/docs/rally @@ -0,0 +1,46 @@ +Rally (http://www.rallydev.com) is the recognized leader in Agile +application lifecycle management (ALM). Rally is dedicated to helping +organizations embrace Agile and Lean development and portfolio management +practices that dramatically increase the pace of innovation, improve +product quality and boost customer satisfaction. + +The GitHub-Services integration for Rally creates Changeset/Change +information in Rally associated with the Workspace and SCMRepository +of your choice. In Rally, when looking at an artifact, you can see +detail on Changesets associated with commits from GitHub push activities. +Rally also provides reports that use the Artifact, Changeset and Change +information to provide insight into files that get changed frequently +or that are associated with higher than normal defect rates and other +useful metrics based reports. + +The integration scans for tokens in the commit message that conform to a +Rally Artifact FormattedID format. These tokens are validated against +Rally so that a reference to a valid Rally Artifact (UserStory, Defect, +TestCase, Task, etc.) results in the association of the Artifact to the +Changeset posted in Rally performed by this integration. +Commits containing a commit message that doesn't have a valid artifact +reference also get posted to Rally so that they show up in any Rally +reports against Changesets/Changes. + +NB: Uri fields in Rally Changeset/Change records refer the to master branch. + +Install Notes +------------- + +You'll need a Rally account (see http://rally.rallydev.com). + +1. server - (REQUIRED) is the hostname of the Rally server. + You do not have to provide the domain name portion of the server + if the name of the server is a Rally SaaS server, + ie., rally1 or trial or demo. + If you are utilizing the Rally OnPrem product, you'll need + to provide fully qualified hostname. +2. username - (REQUIRED) is the Rally UserName for the subscription. + Make sure you specify the UserName (not the DisplayName or FirstName and LastName or Email). +3. password - (REQUIRED) is the password associated with the UserName for the Rally Subscription. +4. workspace - (REQUIRED) is the name of the Rally Workspace in which the target SCMRepository + resides. +5. repository - (OPTIONAL) is the name of the Rally SCMRepository. By default, if not provided, + this integration will use the name of your GitHub repository as the SCMRepository name. + But, you have the ability to name the Rally SCMRepository to your choice of a name. + diff --git a/docs/rationalteamconcert b/docs/rationalteamconcert new file mode 100644 index 000000000..811852d14 --- /dev/null +++ b/docs/rationalteamconcert @@ -0,0 +1,25 @@ +This service integrates GitHub with Rational Team Concert (http://www.jazz.net). The service automatically adds comments into work item specified by the commit message. Additionally, this hook allows you to create new work items from commits. + +Use the pattern "[#] Commit text" to add a comment to the work item with the number specified in the commit message. E.g. + "[#32] Fix an annoying bug." + +Note that the '#' is optional. + +Use the pattern "[workItemType] Fix an annoying bug" to create a new work item for type workItemType. Note, that you have to enter the workItemType id instead of the name. E.g. + "[defect] Fox an annoying bug" + +This implementation supports both form based and basic authentication. + +Install Notes +------------- + +A user with the correct licenses to add comments and create work items needs to be created. + +Configuring the GitHub hook: + +1. Server Url - This is your server url (including the jazz application suffix). A valid example would be https://yourdomain:port/jazz or https://yourdomain:port/ccm. +2. Username - This is the username of the user used to access your Rational Team Concert instance. +3. Password - This is the password of the user used to access your Rational Team Concert instance. +4. Project Area UUID - This is the uuid of your project area. It is needed to create new work items. You can find out the uuid by accessing your project using the web ui (using the admin panel). The uuid will be after the itemId parameter in the url. E.g. itemId=_VZsIQOehEeGMALe74yZ8ZQ. Enter just the itemId, which in this example is _VZsIQOehEeGMALe74yZ8ZQ +5. Basic Authentication - Check this option if you have configured your server to use basic authentication instead of form authentication. +6. No Verify SSL - Check this option if you need the hook to skip the verification of your server's SSL certificate. Not recommended for production use, but necessary if your server is running with a self-signed certificate or with a wildcard certificate, since the current Ruby level does not understand SNA/SNI. diff --git a/docs/rdocinfo b/docs/rdocinfo index 3b63f46a2..43a06d289 100644 --- a/docs/rdocinfo +++ b/docs/rdocinfo @@ -1,15 +1,2 @@ -rubydoc.info -========= - This service allows you to auto-publish documentation for your Ruby gem or library. -The resulting documentation will be hosted for you at http://rubydoc.info/github/your-name/your-project - - -Developer Notes ---------------- - -data - - (no data is required, payload is simply posted to http://rubydoc.info/checkout) - -payload - - refer to docs/github_payload +The resulting documentation will be hosted for you at http://rubydoc.info/github/your-name/your-project \ No newline at end of file diff --git a/docs/read_the_docs b/docs/read_the_docs deleted file mode 100644 index b4ac06f0f..000000000 --- a/docs/read_the_docs +++ /dev/null @@ -1,15 +0,0 @@ -ReadTheDocs -=========== - -Automatically build documentation hosted on readthedocs.org. - - -Developer Notes ---------------- - -data - - (no data required; payload is sent to http://readthedocs.org/github) - -payload - - refer to docs/github_payload - diff --git a/docs/readthedocs b/docs/readthedocs new file mode 100644 index 000000000..78a617de4 --- /dev/null +++ b/docs/readthedocs @@ -0,0 +1 @@ +Automatically build documentation hosted on readthedocs.org. \ No newline at end of file diff --git a/docs/redmine b/docs/redmine index 6a99f7cce..96985817d 100644 --- a/docs/redmine +++ b/docs/redmine @@ -1,5 +1,7 @@ -Redmine -========== +Redmine Service has the following modules: + +1. Fetch GitHub Commits +----------------------- Notifies an instance of Redmine, a project management tool, to fetch the new commits from GitHub. @@ -7,23 +9,44 @@ Install Notes ------------- 1. Download and install Redmine from http://redmine.org -2. Activate "Enable WS for repository management" in the global settings. While there, generate an API key (if neccessary) and copy it. -3. Set your github repository as git repository for a project. +2. Activate "Enable WS for repository management" in the global settings. While there, generate an API key (if necessary) and copy it. +3. Set your GitHub repository as git repository for a project. 4. Enter the full URL to your Redmine instance as well as the project's identifier and the API key. -5. We will then notify your Redmine whenever you do a "git push". +5. Check the "Fetch GitHub Commits" option to enable the module. +6. We will then notify your Redmine whenever you do a "git push". Note: The GitHub servers need to have access to "sys/fetch_changesets". Some older tutorials have you add a rule to your server config to allow access to sys/ only from localhost. Since it is now possible to use an API key, this is no longer necessary. Notifies an instance of Redmine, a project management tool, to fetch the new commits from GitHub. -Developer Notes ---------------- +2. Update Redmine Issues about related commits +---------------------------------------------- + +Updates an issue on Redmine, a project management tool, whenever a commit happened and is related to this issue +Commits which are related to Redmine issues are detected by matching '#IssueNo' in the commit message (i.e "fixing bug #234" is related to issue #234) + +Install Notes +------------- +1. Generate API key from Redmine from My account -> API access key (Right Side Section) -> Show +3. This module requires setting Redmine url and API access Key. +4. Check the "Update Redmine Issues About Commits" option to enable the module. + +Note: Evey post update on the issues will be authored by the account given the API access key + +Example of the update issue post on Redmine (the output): + + Commit: b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33 + https://github.com/modsaid/github-services/commit/b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33 + Author: modsaid + Date: 2007-10-10 (Sun, 02 Aug 2012) + Log Message: + ----------- + Fix #{IssueNo} -data - - address (full URL of the redmine instance) - - api_key (the API key for using the webservice) - - project (identifier of the project belonging to this repository) + Credits + ------- + * modsaid + * basayel -payload - - refer to docs/github_payload (nothing is used by this script) + http://www.espace.com.eg diff --git a/docs/rubyforge b/docs/rubyforge deleted file mode 100644 index cb2e59b39..000000000 --- a/docs/rubyforge +++ /dev/null @@ -1,30 +0,0 @@ -RubyForge -========= - -Special Thanks -------------- - -Would like to notice Ara Howard and his RubyForge gem, from which I copied most of the code -used in this service. Would also like to notice the author of the Basecamp service, since -I used that service as a template for the RubyForge service. - -This service simply posts a new NewsByte to the RubyForge project (as specified by the groupid). - -Install Notes -------------- - - 1. username should be the username of a user that has access to the RubyForge project - 2. password should be the password of the given user - 3. groupid should be the group id of the RubyForge project - - -Developer Notes ---------------- - -data - - username - - password - - groupid - -payload - - refer to docs/github_payload diff --git a/docs/run_code_run b/docs/run_code_run deleted file mode 100644 index 916e577e0..000000000 --- a/docs/run_code_run +++ /dev/null @@ -1,16 +0,0 @@ -RunCodeRun -========== - -RunCodeRun is a hosted continuous integration service. It is free for public -projects. For private project plans, see http://runcoderun.com/plans. - -Install Notes -------------- - - 1. Check the "Active" checkbox and click "Update Settings". - 2. Click on the "RunCodeRun" service name and then click the "Test Hook" link. - 3. You should receive an email from RunCodeRun letting you know your - project was setup. - 4. If this is a private project, add the rcr-private user as a collaborator. - -For more details, go to http://runcoderun.com/github diff --git a/docs/scrumdo b/docs/scrumdo index 1b390205a..96e9c8e2b 100644 --- a/docs/scrumdo +++ b/docs/scrumdo @@ -1,6 +1,3 @@ -ScrumDo -======= - ScrumDo is an agile/scrum project management site found at http://www.scrumdo.com Integrating GitHub with ScrumDo lets you see commits in your project newsfeed, and allows you to update stories via commit messages. @@ -22,13 +19,3 @@ Install Notes Your project slug is `acme-project` -Developer Notes ---------------- - -data - - username - - password - - project_slug - -payload - - refer to docs/github_payload diff --git a/docs/sifter b/docs/sifter new file mode 100644 index 000000000..8e41e451b --- /dev/null +++ b/docs/sifter @@ -0,0 +1,13 @@ +Sifter is a simple issue tracker designed explicitly for teams with non-technical team members. (https://sifterapp.com) + +The integration enables you to comment on, resolve, and close issues via commit messages. + +Install Notes +------------- + +0. Sign up for Sifter (https://sifterapp.com) +1. Create a new project. +2. Go to your Sifter project +3. Go to the "Project Settings" > "Integrations" page. +4. Copy the token on the page for GitHub and add it here along with your account's subdomain. +5. Any team members that want to use the integration will need to visit their profile page and explicitly link their Sifter user account with their GitHub user account so that we can identify individuals from their commits. \ No newline at end of file diff --git a/docs/simperium b/docs/simperium new file mode 100644 index 000000000..038d508f0 --- /dev/null +++ b/docs/simperium @@ -0,0 +1,25 @@ +This service posts an event object for GitHub events to a Simperium app. +A Simperium object is created for each hook event in a bucket you choose. + +Supported events are currently: +'push', 'issues', 'issue_comment', 'commit_comment', 'pull_request', +'pull_request_review_comment', 'watch', 'fork', 'fork_apply', +'member', 'public', 'team_add', 'status' + +Simperium IDs: +Each object ID will be a guid, eg. 'guid-0.3939192' + +Data: +'event' - The GitHub event type, eg. 'push' for commits +'payload' - The data for the event, varies by event type. + +More details on GitHub events: https://developer.github.com/v3/repos/hooks/ + +Install Notes +------------ + +App ID: Your Simperium app id created on https://simperium.com/dashboard +Token: You'll need to generate an access token for a specific user (not an API key) +Bucket: Name of the bucket you want events posted to + +See https://simperium.com/docs/reference/http/#auth for more details on Simperium authentication. diff --git a/docs/skydeskprojects b/docs/skydeskprojects new file mode 100644 index 000000000..8ba3000b2 --- /dev/null +++ b/docs/skydeskprojects @@ -0,0 +1,19 @@ +SkyDesk Projects Requirements: + +Log into the SkyDesk portal first. +SkyDesk Portal URL : https://www.skydesk.jp +Then access SkyDesk projects using https://projects.skydesk.jp. + +Project ID and Token is required for configuration which will be available under "Dashboard" --> "Project Settings" --> "Service Hooks". + +This service hook allows you to associate changeset(s) with bug(s) in SkyDesk Projects. To associate changeset(s) with bug(s) in SkyDeskProjects you will need to give the BUG ID in in your commit message. + +Syntax: `OPEN SQUARE BRACKET #BUGID CLOSE SQUARE BRACKET` followed by commit message + +Ex: `[#SDP-23] fixed the memory leak issue.` + +This will associate the changeset with bug with ID SDP-23. + +For more than one bugs, provide the BUG IDS separated by comma. + +Ex: `[#SDP-24,#SDP-25] UI alignment fix done.` diff --git a/docs/smartling b/docs/smartling new file mode 100644 index 000000000..490c6f508 --- /dev/null +++ b/docs/smartling @@ -0,0 +1,10 @@ +The [Smartling](http://www.smartling.com/) platform simplifies and accelerates translation and localization of your websites, mobile apps, and documents. + +Install Notes +------------- + +1. **Service url** - URL where your _Repository Connector service_ is run +2. **Project ID** - Your Smartling Project identifier code. You can find it and your **API key** in [Smartling dashboard -> Project Settings -> API](https://dashboard.smartling.com/settings/api.htm) +3. **API key** - Your Smartling API key +4. **Config path** - Relative path to the Smartling configuration file in your repository. By default the file name is expected to be `smartling-config.json` +5. **Master only** - If this flag is checked only changes to the `master` branch will be uploaded to Smartling diff --git a/docs/snowyevening b/docs/snowyevening new file mode 100644 index 000000000..6aea38780 --- /dev/null +++ b/docs/snowyevening @@ -0,0 +1,10 @@ +Notifies your project account on Snowy-Evening.com of commits. + +Install Notes +------------- + +1. Login or create a free or paid account with Snowy-Evening.com +2. Create a project and visit the Project Edit page +3. Find the GitHub integration box, which contains the API Key and Project ID +4. Copy those credentials into these fields. + diff --git a/docs/socialcast b/docs/socialcast deleted file mode 100644 index fb83f9c0c..000000000 --- a/docs/socialcast +++ /dev/null @@ -1,26 +0,0 @@ -Socialcast -========== - -Install Notes -------------- - - You should already have a Socialcast community. For the group_id you'll need to call the - API to figure out the underlying integer group id for a group. - - - 1. api_domain (e.g. foo-com.socialcast.com) - 2. username (user to post message on behalf of) - 3. password (password of user to post message on behalf of) - 4. group_id (OPTIONAL Id of a Socialcast group to post the message into) - -Developer Notes ---------------- - -data - - api_domain - - username - - password - - group_id (optional) - -payload - - refer to docs/github_payload diff --git a/docs/softlayermessaging b/docs/softlayermessaging new file mode 100644 index 000000000..aee391d15 --- /dev/null +++ b/docs/softlayermessaging @@ -0,0 +1,12 @@ +Install Notes +------------- + + + "SoftLayer Message Queue" getting started guide: + http://sldn.softlayer.com/article/Message-Queue-Getting-Started + + 1. account is the publishing account, typically 5 characters and not the username + 2. user is the authenticating portal user + 3. key is the messaging API key (not SoftLayer API key). + 4. name is the queue or topic name. This will need to be created before hand. + 5. topic designates whether the name provided is either a topic or a queue name. diff --git a/docs/sourcemint b/docs/sourcemint deleted file mode 100644 index 3391cf6fd..000000000 --- a/docs/sourcemint +++ /dev/null @@ -1,5 +0,0 @@ -Sourcemint -========== - -Notify the [Sourcemint](http://sourcemint.com/) package repository and build service when a new release has been tagged -or changes have been pushed to a branch. diff --git a/docs/splendid_bacon b/docs/splendid_bacon deleted file mode 100644 index 4db9de470..000000000 --- a/docs/splendid_bacon +++ /dev/null @@ -1,18 +0,0 @@ -Splendid Bacon -=============== - -Install Notes -------------- - - 1. Token is your project token from Splendid Bacon. This is found on the project edit page on http://splendidbacon.com - 2. Project ID is a unique identifier for your project. This is found on the project edit page or in any URL for the project. - -Developer Notes ---------------- - -data - - token - - project_id - -payload - - refer to docs/github_payload diff --git a/docs/sprintly b/docs/sprintly new file mode 100644 index 000000000..5d3451338 --- /dev/null +++ b/docs/sprintly @@ -0,0 +1,11 @@ +Sprint.ly is a project management aide which is lightweight for developers and provides visibility for external stakeholders. + +Integration with sprint.ly powers closing tickets through commit messages, updating ticket status via pull request and much more. + +Install Notes +------------- + +1. Sign up at https://sprint.ly/ +2. On your profile page ( https://sprint.ly/account/profile/ ), get your API key. +3. On your product's page, get the product number. The id is in the URL like: https://sprint.ly/product/3722/ -- 3722 would be your product id. +4. Enjoy! diff --git a/docs/sqsqueue b/docs/sqsqueue new file mode 100644 index 000000000..43d98cbbd --- /dev/null +++ b/docs/sqsqueue @@ -0,0 +1,12 @@ +Install Notes +------------- + +The Amazon SQS service allows GitHub to send a notification to an Amazon SQS queue. + +1. Configure your Amazon AWS account with an appropriately set up access-key/secret-key (Either parent account or IAM) + that has permissions to perform 'SendMessage' operations. (https://console.aws.amazon.com/sqs/) + +2. Amazon SQS ARN is the unique code SQS references your queue with. It can be copied from the console + or fetched through the API. It takes the form of arn:aws:sqs:us-(region)-(dc):(id):(name). Copy and + paste the entire string. This hook parses the necessary details from the arn. + You may also specify a SQS Queue URL instead of an ARN if you want to use IAM or a Queue that was created using a different account. diff --git a/docs/stackmob b/docs/stackmob deleted file mode 100644 index e6ecb02a4..000000000 --- a/docs/stackmob +++ /dev/null @@ -1,18 +0,0 @@ -StackMob Deploy -============== - -Deploys this Repository to StackMob after each push. - -Install Notes: -------------- - - 1. Token - Your [StackMob GitHub Token](https://stackmob.com/platform/account/versioncontrol/settings) - -Developer Notes ---------------- - -data - - token - -payload - - refer to docs/github_payload \ No newline at end of file diff --git a/docs/statusnet b/docs/statusnet index 596656185..7a78d4131 100644 --- a/docs/statusnet +++ b/docs/statusnet @@ -1,20 +1,4 @@ -Statusnet (laconica) -==================== - Install Notes ------------- - 1. the server field should be the server base URI.(eg. http://identi.ca or http://identi.ca/index.php) - - -Developer Notes ---------------- - -data - - username - - password - - digest (boolean) - - server - -payload - - refer to docs/github_payload + 1. **Server** should be the server base URI.(eg. http://identi.ca or http://identi.ca/index.php) \ No newline at end of file diff --git a/docs/talker b/docs/talker index 3de184d42..2f489977a 100644 --- a/docs/talker +++ b/docs/talker @@ -1,22 +1,8 @@ -Talker -======== - Install Notes ------------- - 1. url should be your room url, for example https://youraccount.talkerapp.com/rooms/ROOM_ID - 2. token should be on your settings page - 3. if digest is checked, all commits will be compressed in one message - -Note: replace https with http on the url if you're on the Free plan as it doesn't include enhanced security (SSL). - -Developer Notes ---------------- - -data - - url - - token - - digest (boolean) + 1. **Url** should be your room url, for example https://youraccount.talkerapp.com/rooms/ROOM_ID + 2. **Token** should be on your settings page + 3. If **Digest** is checked, all commits will be compressed in one message -payload - - refer to docs/github_payload +Note: replace https with http on the url if you're on the Free plan as it doesn't include enhanced security (SSL). \ No newline at end of file diff --git a/docs/target_process b/docs/targetprocess similarity index 67% rename from docs/target_process rename to docs/targetprocess index d7307b6bc..bdeccab58 100644 --- a/docs/target_process +++ b/docs/targetprocess @@ -1,14 +1,7 @@ -TargetProcess -============= - -------------------------------------------- -TargetProcess - GitHub Integration Overview -------------------------------------------- - Integration with TargetProcess allows GitHub user to change current entity states and add comments directly from their commit messages. --------------- + Install Notes -------------- @@ -28,33 +21,19 @@ Install Notes - A project admin's account credentials to access your TargetProcess server. - - The project id from your TargetProcess project that the GitHub repository - should map to (this can be found on the "Projects" list within TargetProcess) ---------------------- Commit Message Syntax --------------------- General comment format for adding comments and/or changing entity states: - #[Entity ID]: - Eg: Fixed out of memory exceptions #1221:Fixed + (#|id:)[Entity ID]: + Eg: Fixed out of memory exceptions #1221:Fixed + id:1221:Fixed Fixed out of memory exceptions The entire line will appear as a comment in your User Story, Bug, Feature, or Task; and the entity's state will be changed to the state name, if specified. A new state is not required, and an Assignable's state will not change unless it is specified. Entity ID is required. This text sequence can appear anywhere in your commit message. - - -Developer Notes ---------------- - -data --base_url --username --password --project_id - - diff --git a/docs/tddium b/docs/tddium new file mode 100644 index 000000000..19077d569 --- /dev/null +++ b/docs/tddium @@ -0,0 +1,30 @@ +Tddium Continuous Integration + +Learn more at [tddium.com](https://www.tddium.com). + +Install Notes +------------- + +This hook will be automatically configured for GitHub repos you've configured +with a connected Tddium account. + +If you don't want to connect your GitHub account, or you don't want to give +Tddium `repo` privileges, follow these steps: + +1. Go to your Tddium dashboard +2. Open the Configuration page for this repo and click on the 'CI Setup' section +3. Copy the hex value listed under 'CI Token' +4. Paste the token above. +5. Install Tddium's repo-specific public key into this repo's deploy keys. + +You can safely leave the Override URL field blank. + +Supported Events +---------------- + +Tddium monitors all events on your repo, but it currently only acts on: + +- push +- create a branch +- delete a branch +- open, close, synchronize pull requests diff --git a/docs/teamcity b/docs/teamcity index 5d4adeefd..42eb39483 100644 --- a/docs/teamcity +++ b/docs/teamcity @@ -1,35 +1,39 @@ -TeamCity -======= +[TeamCity](https://www.jetbrains.com/teamcity/) is a continuous integration server that automates building and testing of your software. -TeamCity is a continuous integration server that automates the building and testing of your software. -The github TeamCity service can be used to trigger builds after code has been pushed to your git repository. + +The GitHub TeamCity service can be used in two ways: +* to trigger builds after code has been pushed to your git repository; (Default) +* to enforce checking for changes after code has been pushed to your git repository. (see 7) Install Notes ------------- - 1. Your TeamCity server must be accessible from the internet. +1. Your TeamCity server must be accessible from the internet. - 2. "base_url" is the URL to your TeamCity server - Example: https://teamcity.example.com/ or http://teamcity.example.com/teamcity/ +2. "Base url" is the URL to your TeamCity server + Example: https://teamcity.example.com/ or http://teamcity.example.com/teamcity/ - 3. "build_type_id" is the identifier of the build configuration you want to trigger - Example: "bt123", which can be found in URL of the build configuration page ("...viewType.html?buildTypeId=bt123&...") +3. "Build type" is the identifier of the build configuration you want to trigger. + Multiple configurations can be triggered by specifying a comma-separated list of identifiers. + Example: "bt123", which can be found in URL of the build configuration page + ("...viewType.html?buildTypeId=bt123&...") - 4. "username" and "password" - username and password of a TeamCity user that can - trigger a manual build of the TeamCity build configuration defined in "build_type_id" +4. "Username" and "Password" - username and password of a TeamCity user that can + trigger a manual build of the TeamCity build configuration defined in "Build type" - 5. Since TeamCity uses BASIC authentication, it is highly recommended that the TeamCity server - use HTTPS/SSL to protect the password that is passed unencrypted over the internet. +5. "Branches" (Optional) is an space-separated list of branches to watch commits for. + Commits to other branches will be ignored. + If no branches are specified, TeamCity will be notified of all commits. +6. "Full branch ref" if enabled full branch reference (e.g. 'refs/heads/master') + will be send to TeamCity server. Otherwise 'refs/heads' will be omitted. -Developer Notes ---------------- +7. "Check for pending changes" if enabled, service will force TeamCity server + to check for pending changes. Service will not trigger new build. -data - - base_url - - build_type_id - - username - - password +8. Since TeamCity uses BASIC authentication, it is highly recommended that the + TeamCity server use HTTPS/SSL to protect the password that is passed + unencrypted over the internet. -payload - - unused by this service diff --git a/docs/tender b/docs/tender new file mode 100644 index 000000000..7f8e4f80f --- /dev/null +++ b/docs/tender @@ -0,0 +1,9 @@ +Install Notes +------------- + +If your URL is http://mysite.tenderapp.com, then: + +* Your **domain** is `mysite.tenderapp.com` + +* You can find your token in the tracker settings: + http://mysite.tenderapp.com/settings/trackers diff --git a/docs/test_pilot b/docs/test_pilot deleted file mode 100644 index 3e197609d..000000000 --- a/docs/test_pilot +++ /dev/null @@ -1,27 +0,0 @@ -TestPilot CI -====== - -TestPilot CI is a fully managed distributed Continuous Integration and Deployment Service -for Ruby, Node.js, Clojure, Java, Python, and Scala applications. - -Install Notes -------------- - - 1. Create an account at http://testpilot.me - 2. Authorize TestPilot to connect with your GitHub account. - 3. After your repositories have been synced, activate the repository you want to test. - TestPilot will automatically add necessary token above for your project, alternatively you can - find the token under your account page at http://testpilot.me/my/account - - 4. Check the "Active" checkbox and click "Update Settings". - 5. Push some changes to this repository and TestPilot will automatically start your build process. - 6. You should receive an email from TestPilot once the build has completed - -For more details about TestPilot, go to http://testpilot.me - -Developer Notes ---------------- - -data - - token - diff --git a/docs/toggl b/docs/toggl index c443b8f51..3364d0bf6 100644 --- a/docs/toggl +++ b/docs/toggl @@ -1,20 +1,4 @@ -toggl -======== - -Install Notes -------------- - - 1. API Key: Your toggl api key. (Get it at https://www.toggl.com/user/edit) - 2. Project: the id of the toggl project that this repo links to - 3. Activate and go! - 4. To track your time simply add t:number-of-minutes (integer) to your commit message - -Developer Notes ---------------- - -data - - api_key - - project - -payload - - refer to docs/github_payload +1. API Key: Your toggl api key. (Get it at https://toggl.com/app/profile) +2. Project: the id of the toggl project that this repo links to +3. Activate and go! +4. To track your time simply add t:number-of-minutes (integer) to your commit message diff --git a/docs/trac b/docs/trac index 0b12db48d..c212dc4e1 100644 --- a/docs/trac +++ b/docs/trac @@ -1,20 +1,7 @@ -Trac -==== +This service is deprecated. To integrate GitHub with Trac, follow the +instructions at: https://github.com/aaugustin/trac-github -Install the following plugin on your Trac server before proceeding: http://github.com/davglass/github-trac/ +If you're currently relying on this service, please consider upgrading to +trac-github. It uses a standard webhook to provide the same features and it's +more actively maintained. - 1. url field is your Trac install's url - 2. token (this needs to be the same token you put in your trac.ini installed via the plugin) - - -The plugin searches commit messages for text in the form of: - command #1 - command #1, #2 - command #1 & #2 - command #1 and #2 - -Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.: - command ticket:1 - command ticket:1, ticket:2 - command ticket:1 & ticket:2 - command ticket:1 and ticket:2 diff --git a/docs/trajectory b/docs/trajectory deleted file mode 100644 index e39fddbf6..000000000 --- a/docs/trajectory +++ /dev/null @@ -1,9 +0,0 @@ -Trajectory -========== - -Install Notes -------------- - - 1. api_key: Your api key can be found in this link: https://www.apptrajectory.com/profile/edit - -All you need to do is copy your api key from the application and paste it into the GitHub service configuration. \ No newline at end of file diff --git a/docs/travis b/docs/travis index fe3b99ebe..c3f87cd90 100644 --- a/docs/travis +++ b/docs/travis @@ -1,32 +1,12 @@ -Travis -====== +Travis CI is a distributed continuous integration service. -Travis – a distributed build server tool for the Ruby community +By enabling this hook, Travis CI will listen to push and pull request events to trigger new builds. +For more details about Travis, go to http://docs.travis-ci.com. -Install Notes -------------- +## Automatic configuration from Travis CI +We recommend using the Travis profile page at https://travis-ci.org/profile to manage your hooks. +For private repositories, use https://travis-ci.com/profile. - 1. Create an account (on travis-ci.org you can just sign in with GitHub) - 2. Enter your credentials - - The token which you can find on the Travis profile page - Optional steps: - - Enter the username who the travis-token belongs to (defaults to the repository owner) - - Enter the host of your Travis installation (defaults to http://travis-ci.org), the protocol-prefix is optional and defaults to "http://". - - 3. Check the "Active" checkbox and click "Update Settings". - 4. Click on the "Travis" service name and then click the "Test Hook" link. - 5. You should receive an email from Travis once the build has completed - -For more details about Travis, go to http://github.com/travis-ci/travis-ci - - -Developer Notes ---------------- - -data - - token - - user - - domain - -The token has to belong to the user, of course. +## Travis CI Status +Travis CI status page: http://status.travis-ci.com diff --git a/docs/trello b/docs/trello new file mode 100644 index 000000000..11696937c --- /dev/null +++ b/docs/trello @@ -0,0 +1,13 @@ +Adds a card to a Trello list for each commit pushed to GitHub, optionally limited to the master branch or by exclusion regex. + +Install Notes +------------- + +1. [Create a consumer token][create] authorizing GitHub to write to the Trello API. +2. Supply the list identifier for the list to be added to. Pull requests can optionally be added to a separate Trello list. You can get the lists for a board at this URL: + https://api.trello.com/1/board/BOARDID?token=TOKEN&key=db1e35883bfe8f8da1725a0d7d032a9c&lists=all + (To edit this URL for your Trello board, replace 'TOKEN' with the consumer token you created in step 1, and BOARDID with the ID of the Trello board that the list is on – the ID is the final section of the board's Trello URL.) +3. If you want to ignore commits with commit messages matching a particular regular expression (i.e. "Merge branch"), put the regex here. +4. If you want to create cards only for commits on the master branch, tick "Master Only." + +[create]: https://trello.com/1/authorize?key=db1e35883bfe8f8da1725a0d7d032a9c&name=GitHub+Services&expiration=never&response_type=token&scope=read,write diff --git a/docs/twilio b/docs/twilio index e9e9da6fd..39a8a3db4 100644 --- a/docs/twilio +++ b/docs/twilio @@ -1,10 +1,7 @@ -Twilio -====== - Allows you to setup a GitHub repository to send out SMS messages on git pushes. Currently the SMS message is built to say: -[pusher name] has pushed [# commits] commit(s) to [repository name] + "#{payload['pusher']['name']} has pushed #{payload['commits'].size} commit(s) to #{payload['repository']['name']}" Install Notes ------------- @@ -13,17 +10,5 @@ Install Notes 2. You can find your account_sid and auth_token on your account page at https://www.twilio.com/user/account 3. from_phone must be a "Twilio phone number enabled for SMS. Only phone numbers or short codes purchased from Twilio work here" 4. to_phone is the "destination phone number. Format with a '+' and country code e.g., +16175551212 (E.164 format)." -5. Check master_only if you only want to recieve updates for master - -Developer Notes ---------------- - -data - - account_sid - - auth_token - - from_phone - - to_phone - - master_only +5. Check master_only if you only want to receive updates for master. -payload - - refer to docs/github_payload diff --git a/docs/twitter b/docs/twitter index a6173c338..6975550e4 100644 --- a/docs/twitter +++ b/docs/twitter @@ -1,29 +1,17 @@ -Twitter -======== - Install Notes ------------- -1. Get an OAuth Token for Your Twitter Account from the services hooks edit page for your repo. -2. You're going to be redirected to twitter to allow GitHub to tweet on your behalf. -3. Be sure that you're logged in as the twitter user you would like to tweet from. - -Developer Notes ---------------- - -data - - token - - secret - - digest (boolean) + 1. [Get an OAuth Token for Your Twitter Account][authorize_url]. + 2. You're going to be redirected to Twitter to allow GitHub to tweet on your behalf. + 3. Be sure that you're logged in as the Twitter user you would like to tweet from. -payload - - refer to docs/github_payload +Check "Digest" to send only the number of recent commits to a branch along with a link to that branches +commit list, instead of tweeting all commits individually with their message and direct link to each commit. -Deployment Notes ----------------- +Check "Short format" to omit the repository and commiter's names from the tweet to get more +space for the commit message. -The secrets.yml file should include an entry for the consumer keys github needs to post for people. +If you would like to only receive messages for a specific branch, add the name (or partial name) to +the "Filter branch" option. Otherwise, leave the field blank to receive messages for all branches. - twitter: - key: - secret: +[authorize_url]: <%= twitter_oauth_path(current_repository.user, current_repository) %> diff --git a/docs/typetalk b/docs/typetalk new file mode 100644 index 000000000..e5d4f1a0c --- /dev/null +++ b/docs/typetalk @@ -0,0 +1,10 @@ +Posts a message to [Typetalk](https://typetalk.in) topics when you push to GitHub. + +Install Notes +------------- + +1. Need to [register new application](https://typetalk.in/my/develop/applications) that authorises GitHub to access to the Typetalk API. (Grant type must be "Client Credentials") +2. Enter your credentials. + - client_id + - client_secret +3. Enter topic id to post messages. diff --git a/docs/unfuddle b/docs/unfuddle index 14b924aee..cd28ebd23 100644 --- a/docs/unfuddle +++ b/docs/unfuddle @@ -1,29 +1,19 @@ -Unfuddle -======== - Unfuddle is a lovely ticket management, time tracking, project hosting service. Install Notes --------------- -Create a new Git repository in your Unfuddle project. -Once created, edit the repository settings. Check the +Create a new Git repository in your Unfuddle project. +Once created, edit the repository settings. Check the "Changesets Managed Manually" checkbox. Make note of the following from the URL: -http://example.unfuddle.com/a#/projects/32429/repositories/28573/browse + http://example.unfuddle.com/a#/projects/32429/repositories/28573/browse In the above, the data you need for configuration is: - - subdomain: example - - repo_id: 28573 - -Developer Notes ---------------- +* subdomain: example +* repo_id: 28573 -data - - repo_id (Unfuddle respository ID) - - subdomain - - username (Unfuddle API account holder) - - password (Unfuddle API account password) +If your URL begins with http:// rather than https:// check the Httponly box when setting up the service hook. diff --git a/docs/weblate b/docs/weblate new file mode 100644 index 000000000..8384aad2d --- /dev/null +++ b/docs/weblate @@ -0,0 +1,12 @@ +This service will notify Weblate about pushes to your repository. +Weblate will then refresh updated translations and merge them with your +changes. + +More info: http://weblate.org/ + +Install Notes +------------- + +You need to have enabled hooks in your Weblate installation (this is default) +see documentation for more information: +https://weblate.readthedocs.org/en/latest/api.html#notification-hooks diff --git a/docs/web_translate_it b/docs/webtranslateit similarity index 69% rename from docs/web_translate_it rename to docs/webtranslateit index 69443129c..ce30b7990 100644 --- a/docs/web_translate_it +++ b/docs/webtranslateit @@ -1,6 +1,3 @@ -Web Translate It -================ - This service notify Web Translate It of pushes to your public repository. Web Translate It will then refresh the language file(s) if they have been updated. More info: http://docs.webtranslateit.com/github-integration/ @@ -8,5 +5,4 @@ More info: http://docs.webtranslateit.com/github-integration/ Install Notes ------------- - API Key is your project API key - (you can find it in the project settings) + * **API Key** is your project API key (you can find it in the project settings) diff --git a/docs/windowsazure b/docs/windowsazure new file mode 100644 index 000000000..3622ee1b0 --- /dev/null +++ b/docs/windowsazure @@ -0,0 +1,14 @@ +This service posts a payload for GitHub 'push' event to a Windows Azure WebSites (WAWS). +This enables continuous integration and deployment with WAWS. + +More Info: http://www.windowsazure.com/en-us/develop/net/common-tasks/publishing-with-git/ + +Supported events are currently: 'push' + +Config: + 'hostname' - The Azure WebSites SCM endpoint hostname. + 'username' - Username basic cred for SCM endpoint. + 'password' - Password basic cred for SCM endpoint. + +Data: + 'payload' - Standard GitHub event payload by event type. diff --git a/docs/xmpp_im b/docs/xmpp_im new file mode 100644 index 000000000..34c9e84bc --- /dev/null +++ b/docs/xmpp_im @@ -0,0 +1,34 @@ +# XMPP IM + +This service will connect to an XMPP account with the provided details and then send a status update to one or more JIDs (bare or full). It then immediately disconnects from the server. + +## Configuration + +1. **JID** - The JID (Jabber ID) with which to connect to the XMPP server +2. **Password** - Password +3. **Receivers** - The JIDs (bare or full) that you'd like to send updates to. JIDS are whitespace separated +4. **Restrict to Branch** - List of branches which will be inspected. +5. **Host** - If you need to set a custom host enter here, otherwise leave blank for DNS lookup. +6. **Port** - If ou need to use a custom port (default: 5222) enter value here. + +### Options + +1. **Active** - Master on/off switch +2. **Notify wiki** - Get notifications of wiki events +3. **Notify comments** - Get notifications of comments +4. **Notify watch** - Get notifictions of watch events +5. **Notify issues** - Get notifications on issues +6. **Notify pull requests** - Get notifications of pull requests + +If you would like to only receive messages for a specific branch, add the name (or partial name) to +the "Restrict to Branch" option. Otherwise, leave the field blank to receive messages for all branches. + +## Notes + +This service currently enables all of the available hook events including +commit comments, downloads, forks, fork applies, wiki updates, issues, issue +comments, member adds, pull requests, pushes, and watches. However, not all +events are posted to the MUC room. If you would like these events supported +please raise an issue, or make a pull request! + +A full list of events with descriptions can be found here: https://developer.github.com/v3/repos/hooks/. diff --git a/docs/xmpp_muc b/docs/xmpp_muc new file mode 100644 index 000000000..d9b015bf0 --- /dev/null +++ b/docs/xmpp_muc @@ -0,0 +1,40 @@ +# XMPP MUC + +This service will connect to an XMPP MUC room with the provided details and then post a status update. It then immediately disconnects from the room. + +## Configuration + +1. **JID** - The JID (Jabber ID) with which to connect to the XMPP server +2. **Password** - Password +3. **Room** - The room you'd like to join +4. **Server** - The server in which the room lives +5. **Nickname** - The nickname that should be used (defaults to __github__) +6. **Room password** - The password (if required) to join the MUC room +7. **Restrict to Branch** - List of branches which will be inspected. +8. **Host** - If you need to set a custom host enter here, otherwise leave blank for DNS lookup. +9. **Port** - If ou need to use a custom port (default: 5222) enter value here. + +### Options + +1. **Active** - Master on/off switch +2. **Notify wiki** - Get notifications of wiki events +3. **Notify comments** - Get notifications of comments +4. **Notify watch** - Get notifictions of watch events +5. **Notify issues** - Get notifications on issues +6. **Notify pull requests** - Get notifications of pull requests + +If you would like to only receive messages for a specific branch, add the name (or partial name) to +the "Restrict to Branch" option. Otherwise, leave the field blank to receive messages for all branches. + +## Notes + +You may wish to reserve the nickname for the user in question so that it will +always be able to connect to the room. + +Note: This service currently enables all of the available hook events including +commit comments, downloads, forks, fork applies, wiki updates, issues, issue +comments, member adds, pull requests, pushes, and watches. However, not all +events are posted to the MUC room. If you would like these events supported +please raise an issue, or make a pull request! + +A full list of events with descriptions can be found here: https://developer.github.com/v3/repos/hooks/. diff --git a/docs/yammer b/docs/yammer deleted file mode 100644 index 7bd539c86..000000000 --- a/docs/yammer +++ /dev/null @@ -1,30 +0,0 @@ -Yammer -====== - -Install Notes -------------- - -1. Create a new Yammer user that will post all of your commit messages by proxy (e.g. git@organization.tld) - -2. Login as this user and go to https://www.yammer.com/client_applications/new - -3. Create a new application, for example: - - Application name: organization-git - Author: organization - Author Email: git@organization.tld - Main Application URL: http://organization.tld/ - -4. Save the returned Consumer (Application) Key and Consumer (Application) Secret. You will need them for the next step. - -5. Authorize the application. You can do this by using the yammer_create_oauth_yml.rb script of the yammer4r gem. - -*** Note: on my system (ruby 1.8.7 / OS X 10.6.4) I had to make a (very) small patch to this file to get it - to complete. It may have been pulled into jstewart's master (http://github.com/jpatterson/yammer4r) by - the time you read this -- but if not you can grab mine here at http://github.com/rboyd/yammer4r - - Don't worry, if it fails on the first attempt you can always try again without issue. *** - -6. Enter the output of step 5 (consumer key+secret and access token+secret) into your github service hook form for yammer. - - diff --git a/docs/you_track b/docs/you_track deleted file mode 100644 index c94262575..000000000 --- a/docs/you_track +++ /dev/null @@ -1,77 +0,0 @@ -YouTrack -======== - ---------------------------------------- -YouTrack - GitHub Integration Overview ---------------------------------------- - -Integration with YouTrack allows GitHub user to apply commands to YouTrack issues -right from commit comments. - --------------- -Install Notes --------------- - -1. YouTrack Requirements - - - Requires YouTrack version 2.0 or above. YouTrack Energy EAP builds are - supported as well. - - - Your YouTrack server should be acessible from the internet. - - - REST API must be enabled in your YouTrack server. - - - Committer's email addresses in GitHub and YouTrack should be the same. - YouTrack looks for user account of a committer by an email address, which the - committer has registered in GitHub. If YouTrack doesn't find a user account with the - same email as committer's email, a command specified in commit's comment won't be applied. - -2. Settings in GitHub - - In the GitHub integration config, the following settings should be provided: - - - YouTrack Server URL - - - Administrator's account (e.g. 'root' user) credentials to access your YouTrack - server. - - - Name of a user group in YouTrack, in which YouTrack will search for committer's - account. - ---------------------------------- -Essential YouTrack Configuration ---------------------------------- - -- Enable REST API via 'Administration' > 'Settings' section on your YouTrack - server. - -- Use an existing user group (or create a new one) with all users who commit changes - in GitHub. - ---------------- -Command Syntax ---------------- - -General comment format for applying commands: - - - [some comment text] #issueID [command_2] ... [command_n] - [some comment text] #issueID [command_2] ... [command_n] - ... - - -*Important note:* -YouTrack tries to parse as a command any string after a hash mark (#) with an issueID till the end of the line. -If it contains any incorrect commands or attributes, the command will not be applied. If there is no command after -issue, that issue is marked as Fixed. - -Developer Notes ---------------- - -data --base_url --username --password --committers - - diff --git a/docs/youtrack b/docs/youtrack new file mode 100644 index 000000000..741b3aa28 --- /dev/null +++ b/docs/youtrack @@ -0,0 +1,54 @@ +This service processes commit comments and pull request descriptions and searches for YouTrack issue id's and commands. +Commands are applied to issues, associated with commit or pull request. + +1. YouTrack Requirements + + - Requires YouTrack version 2.0 or above. YouTrack Energy EAP builds are + supported as well. + + - Your YouTrack server should be accessible from the internet. + + - REST API must be enabled in your YouTrack server. It can be set in YouTrack Settings menu. + + - Committer's email addresses in GitHub and YouTrack should be the same. + YouTrack looks for user account of a committer by an email address, which the + commiter has registered in GitHub. If YouTrack doesn't find a user account with the + same email as committer's email, a command specified in commit's comment won't be applied. + +2. Settings in GitHub + + In the GitHub integration config, the following settings should be provided: + + - Base url: YouTrack Server URL + + - Committers: Name of a user group in YouTrack, in which YouTrack will search for committer's account. + + - Username: Administrator's account (e.g. 'root' user or Admin role) credentials to access your YouTrack server. + + - Branch: Branch names to track separated by space. If branches are provided, only commits to those branches will trigger + YouTrack commands and commits to others will be ignored. If the branch field is left empty, commits on any branch will trigger commands. + + - Process distinct: If only distinct commits should be processed. If this setting is false, same commit may be processed several times + (for example, when branches are merged) + + - Password: a password of YouTrack Username. Username should have Admin role. + + +* Additional Information + + - [Introduction to invoke YouTrack commands by GitHub comments](http://blog.jetbrains.com/youtrack/2011/04/integrate-youtrack-with-github/) + + - [Official integration documentation](http://confluence.jetbrains.com/display/YTD5/GitHub+Integration) + + - [Setup walkthrough video](http://www.youtube.com/watch?v=0iK1J_fWhns) + + - [YouTrack commands grammar](http://confluence.jetbrains.com/display/YTD5/Command+Grammar) + + +* Troubleshooting + + - GitHub hook errors + + - Authentication problem: You may have to regenerate token in `GibHub > Applications > Personal access tokens` menu and enter it at YouTrack project `settings > GitHub > Edit` menu. + + - 400: Failed to parse YouTrack commands from GitHub commit comments. [Related answer](http://stackoverflow.com/a/26540723/361100) diff --git a/docs/zendesk b/docs/zendesk new file mode 100644 index 000000000..00a8779e8 --- /dev/null +++ b/docs/zendesk @@ -0,0 +1,13 @@ +Zendesk is the fastest way to great customer service. Use this service hook to selectively update tickets with private comments based on GitHub commit messages and issue updates. + +In order to update a Zendesk ticket, all you need to do is to configure and enable this hook, and then subsequently reference the ticket you want updated with the GitHub status. The format of the ticket reference is ZD#123 where 123 is the id of the ticket. Only commits/comments containing a valid ZD ticket reference are sent to Zendesk. + +Install Notes +------------- + + 1. The subdomain is your Zendesk subdomain, i.e. "subdomain" if you visit https://subdomain.zendesk.com + + 2. Username is the email address of the user you wish to authenticate as + + 3. Password is either the password of the user, or your Zendesk API token from "Settings / Channel / API". Remember to adjust the username if you use the API token. + diff --git a/docs/zohoprojects b/docs/zohoprojects index 33579da22..3afd4c82f 100644 --- a/docs/zohoprojects +++ b/docs/zohoprojects @@ -1,38 +1,13 @@ -ZohoProjects -============ +Project ID and Token is required for configuration which will be available under "Dashboard" --> "Project Settings" --> "Service Hooks". -Install Notes -------------- +This service hook allows you to associate changeset(s) with bug(s) in Zoho Projects. To associate changeset(s) with bug(s) in ZohoProjects you will need to give the BUG ID in in your commit message. -Project ID and Token is required for -configuration which will be available -under "Dashboard" --> "Project Settings" ---> "Service Hooks". +Syntax: `OPEN SQUARE BRACKET #BUGID CLOSE SQUARE BRACKET` followed by commit message -This service hook allows you to associate -changeset(s) with bug(s) in Zoho Projects. -To associate changeset(s) with bug(s) in -ZohoProjects you will need to give the -BUG ID in in your commit message. +Ex: `[#SDP-23] fixed the memory leak issue.` -Syntax : OPEN SQUARE BRACKET #BUGID CLOSE SQUARE BRACKET followed by commit message - - Ex : [#SDP-23] fixed the memory leak issue. - - This will associate the changeset with bug with ID SDP-23. - - For more than one bugs, provide the BUG IDS separated by comma. - - Ex : [#SDP-24,#SDP-25] UI alignment fix done. - -Developer Notes ---------------- - -data - - project_id - - token - -payload - - refer to docs/github_payload +This will associate the changeset with bug with ID SDP-23. +For more than one bugs, provide the BUG IDS separated by comma. +Ex: `[#SDP-24,#SDP-25] UI alignment fix done.` diff --git a/github-services.gemspec b/github-services.gemspec new file mode 100644 index 000000000..6f445038c --- /dev/null +++ b/github-services.gemspec @@ -0,0 +1,76 @@ +lib = "github-services" +lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__) +File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/ +version = $1 +sha = `git rev-parse HEAD 2>/dev/null || echo unknown` +sha.chomp! +version << ".#{sha[0,7]}" + +Gem::Specification.new do |spec| + spec.specification_version = 2 if spec.respond_to? :specification_version= + spec.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if spec.respond_to? :required_rubygems_version= + + spec.name = lib + spec.version = version + + spec.summary = "GitHub Services client code" + + spec.authors = ["Rick Olson"] + spec.email = 'rick@github.com' + spec.homepage = 'https://github.com/github/github-services' + spec.licenses = ['MIT'] + + spec.add_dependency "addressable", "~> 2.3" + spec.add_dependency 'yajl-ruby', '>= 1.1.0' + spec.add_dependency "mash", "~> 0.1.1" + spec.add_dependency "mime-types", "~> 1.15" + spec.add_dependency "ruby-hmac", "0.4.0" + spec.add_dependency "faraday", "0.9.0" + spec.add_dependency "xmlrpc", "0.2.1" + + # Basecamp Classic + spec.add_dependency "activeresource", "~> 4.0.0" + + # Twitter + spec.add_dependency "oauth", "0.4.4" + + # MaxCDN + spec.add_dependency "maxcdn", "~> 0.3.2" + + # Campfire + spec.add_dependency "tinder", "1.10.0" + + # Bamboo, Buddycloud + spec.add_dependency "xml-simple", "1.0.11" + + # Email + spec.add_dependency "mail", "~>2.3" + + # Jabber + spec.add_dependency "xmpp4r", "~> 0.5" + + # Twilio + spec.add_dependency "twilio-ruby", "~> 3.9.0" + + # MQTT + spec.add_dependency "mqtt", "0.0.8" + + # Softlayer Messaging + spec.add_dependency "softlayer_messaging", "~> 1.0.2" + + # Amazon SQS, AWS OpsWorks + spec.add_dependency "aws-sdk", "~> 1.64" + + # AWS CodeDeploy, Amazon SNS + spec.add_dependency "aws-sdk-core", "~>2.0.8" + + spec.files = %w(Gemfile LICENSE README.mkdn CONTRIBUTING.md Rakefile) + spec.files << "#{lib}.gemspec" + spec.files += Dir.glob("lib/**/*.rb") + spec.files += Dir.glob("test/**/*.rb") + spec.files += Dir.glob("script/*") + + dev_null = File.exist?('/dev/null') ? '/dev/null' : 'NUL' + git_files = `git ls-files -z 2>#{dev_null}` + spec.files &= git_files.split("\0") if $?.success? +end diff --git a/github-services.rb b/github-services.rb deleted file mode 100644 index b1d06f988..000000000 --- a/github-services.rb +++ /dev/null @@ -1,21 +0,0 @@ -require File.expand_path('../config/load', __FILE__) - -Service::App.set :run => true, - :environment => :production, - :port => ARGV.first || 8080, - :logging => true - -begin - require 'mongrel' - Service::App.set :server, 'mongrel' -rescue LoadError - begin - require 'thin' - Service::App.set :server, 'thin' - rescue LoadError - Service::App.set :server, 'webrick' - end -end - -Service::App.run! - diff --git a/lib/app.rb b/lib/app.rb deleted file mode 100644 index b2d8b2552..000000000 --- a/lib/app.rb +++ /dev/null @@ -1,116 +0,0 @@ -# The Sinatra App that handles incoming events. -class Service::App < Sinatra::Base - - set :hostname, lambda { %x{hostname} } - - # Hooks the given Service to a Sinatra route. - # - # svc - Service instance. - # - # Returns nothing. - def self.service(svc) - post "/#{svc.hook_name}/:event" do - boom = nil - time = Time.now.to_f - data = nil - begin - data = JSON.parse(params[:data]) - payload = parse_payload(params[:payload]) - if svc.receive(params[:event], data, payload) - status 200 - "" - else - status 404 - status "#{svc.hook_name} Service does not respond to 'push' events" - end - rescue Faraday::Error::ConnectionFailed => boom - status 400 - boom.message - rescue Service::ConfigurationError => boom - status 400 - boom.message - rescue Service::TimeoutError => boom - status 400 - "Service Timeout" - rescue Object => boom - report_exception svc, data, boom, - :event => params[:event], :payload => payload.inspect - status 500 - "ERROR" - ensure - duration = Time.now.to_f - time - if duration > 9 - boom ||= RuntimeError.new("Long Service Hook") - report_exception svc, data, boom, - :event => params[:event], :payload => payload.inspect, - :duration => "#{duration}s" - end - end - end - end - - get "/" do - "ok" - end - - # Parses the incoming payload and massages any properties. - # - # json - JSON String. - # - # Returns a Hash payload. - def parse_payload(json) - JSON.parse(json) - end - - # Reports the given exception to Haystack. - # - # exception - An Exception instance. - # - # Returns nothing. - def report_exception(service_class, service_data, exception, options = {}) - backtrace = Array(exception.backtrace)[0..500] - - data = { - 'app' => 'github-services', - 'type' => 'exception', - 'class' => exception.class.to_s, - 'server' => settings.hostname, - 'message' => exception.message[0..254], - 'backtrace' => backtrace.join("\n"), - 'rollup' => Digest::MD5.hexdigest(exception.class.to_s + backtrace[0]), - 'service' => service_class.to_s, - }.update(options) - - if exception.kind_of?(Service::Error) - if exception.original_exception - data['original_class'] = exception.original_exception.to_s - data['backtrace'] = exception.original_exception.backtrace.join("\n") - data['message'] = exception.original_exception.message[0..254] - end - elsif !exception.kind_of?(Service::TimeoutError) - data['original_class'] = data['class'] - data['class'] = 'Service::Error' - end - - if service_class == Service::Web - data['service_data'] = service_data.inspect - end - - if settings.hostname =~ /^sh1\.(rs|stg)\.github\.com$/ - # run only in github's production environment - Net::HTTP.new('haystack', 80). - post('/async', "json=#{Rack::Utils.escape(data.to_json)}") - else - $stderr.puts data[ 'message' ] - $stderr.puts data[ 'backtrace' ] - end - - rescue => boom - $stderr.puts "reporting exception failed:" - $stderr.puts "#{boom.class}: #{boom}" - $stderr.puts "#{boom.backtrace.join("\n")}" - # swallow errors - end -end - -Dir["#{File.dirname(__FILE__)}/../services/**/*.rb"].each { |service| load service } diff --git a/lib/events/helpers/helpers_with_repo.rb b/lib/events/helpers/helpers_with_repo.rb deleted file mode 100644 index d3e1363a6..000000000 --- a/lib/events/helpers/helpers_with_repo.rb +++ /dev/null @@ -1,5 +0,0 @@ -module Service::HelpersWithRepo - def repo - @repo ||= self.class.objectify(payload['repository']) - end -end diff --git a/lib/github-services.rb b/lib/github-services.rb new file mode 100644 index 000000000..b76d5cbdc --- /dev/null +++ b/lib/github-services.rb @@ -0,0 +1,71 @@ +# stdlib +require 'net/http' +require 'net/https' +require 'net/smtp' +require 'socket' +require 'xmlrpc/client' +require 'openssl' +require 'cgi' +#~ require 'date' # This is needed by the CIA service in ruby 1.8.7 or later + +# bundled +require 'addressable/uri' +require 'mime/types' +require 'xmlsimple' +require 'active_resource' +require 'tinder' +require 'yajl/json_gem' +require 'basecamp' +require 'mail' +require 'xmpp4r' +require 'xmpp4r/jid' +require 'xmpp4r/presence' +require 'xmpp4r/muc' +require 'xmpp4r/roster' +require 'oauth' +require 'twilio-ruby' + +# vendor +require 'basecamp' +require 'softlayer/messaging' + +require 'faraday' +require 'faraday_middleware' +require 'ostruct' +require File.expand_path("../service/structs", __FILE__) +require File.expand_path("../service/http_helper", __FILE__) + +class Addressable::URI + attr_accessor :validation_deferred +end + +Faraday::Utils.default_uri_parser = lambda do |url| + uri = if url =~ /^https?\:\/\/?$/ + ::Addressable::URI.new + else + ::Addressable::URI.parse(url) + end + + uri.validation_deferred = true + uri.port ||= uri.inferred_port + uri +end + +XMLRPC::Config::send(:remove_const, :ENABLE_MARSHALLING) +XMLRPC::Config::ENABLE_MARSHALLING = false + +module GitHubServices + VERSION = '1.0.0' + + # The SHA1 of the commit that was HEAD when the process started. This is + # used in production to determine which version of the app is deployed. + # + # Returns the 40 char commit SHA1 string. + def self.current_sha + @current_sha ||= + `cd #{root}; git rev-parse HEAD 2>/dev/null || echo unknown`. + chomp.freeze + end +end + +require File.expand_path('../service', __FILE__) diff --git a/lib/service.rb b/lib/service.rb index 391c41050..094ba06a0 100644 --- a/lib/service.rb +++ b/lib/service.rb @@ -1,10 +1,74 @@ -require 'faraday' -require 'ostruct' - # Represents a single triggered Service call. Each Service tracks the event # type, the configuration data, and the payload for the current call. class Service - dir = File.expand_path '..', __FILE__ + UTF8 = "UTF-8".freeze + class Contributor < Struct.new(:value) + def self.contributor_types + @contributor_types ||= [] + end + + def self.inherited(contributor_type) + contributor_types << contributor_type + super + end + + def self.create(type, keys) + klass = contributor_types.detect { |struct| struct.contributor_type == type } + if klass + Array(keys).map do |key| + klass.new(key) + end + else + raise ArgumentError, "Invalid Contributor type #{type.inspect}" + end + end + + def to_contributor_hash(key) + {:type => self.class.contributor_type, key => value} + end + end + + class EmailContributor < Contributor + def self.contributor_type + :email + end + + def to_hash + to_contributor_hash(:address) + end + end + + class GitHubContributor < Contributor + def self.contributor_type + :github + end + + def to_hash + to_contributor_hash(:login) + end + end + + class TwitterContributor < Contributor + def self.contributor_type + :twitter + end + + def to_hash + to_contributor_hash(:login) + end + end + + class WebContributor < Contributor + def self.contributor_type + :web + end + + def to_hash + to_contributor_hash(:url) + end + end + + dir = File.expand_path '../service', __FILE__ Dir["#{dir}/events/helpers/*.rb"].each do |helper| require helper end @@ -12,6 +76,12 @@ class Service require helper end + ALL_EVENTS = %w[ + commit_comment create delete download follow fork fork_apply gist gollum + issue_comment issues member public pull_request push team_add watch + pull_request_review_comment status release deployment deployment_status + ].sort + class << self attr_accessor :root, :env, :host @@ -21,24 +91,6 @@ class << self end end - # Gets a StatsD client. - def stats - @stats ||= begin - if (hash = secrets['statsd']) && url = hash[env] - uri = Addressable::URI.parse(url) - stats = Statsd.new uri.host, uri.port - stats.namespace = 'services' - stats - else - stats = Statsd.new '127.0.0.1', 8127 - stats.namespace = 'services' - stats - end - end - end - - attr_writer :stats - # The SHA1 of the commit that was HEAD when the process started. This is # used in production to determine which version of the app is deployed. # @@ -51,42 +103,15 @@ def current_sha attr_writer :current_sha - # Public: Processes an incoming Service event. - # - # event - A symbol identifying the event type. Example: :push - # data - A Hash with the configuration data for the Service. - # payload - A Hash with the unique payload data for this Service instance. - # - # Returns true if the Service responded to the event, or false if the - # Service does not respond to this event. + # Returns the Service instance if it responds to this event, or nil. def receive(event, data, payload = nil) - svc = new(event, data, payload) - - methods = ["receive_#{event}", "receive_event"] - if event_method = methods.detect { |m| svc.respond_to?(m) } - Service::Timeout.timeout(20, TimeoutError) do - Service.stats.time "hook.time.#{hook_name}" do - svc.send(event_method) - Service.stats.increment "event.count.#{event}" - end - end + new(event, data, payload).receive + end - true - else - false - end - rescue Service::ConfigurationError, Errno::EHOSTUNREACH, Errno::ECONNRESET, SocketError, Net::ProtocolError => err - Service.stats.increment "hook.fail.config.#{hook_name}" - if !err.is_a?(Service::Error) - err = ConfigurationError.new(err) - end - raise err - rescue Service::TimeoutError - Service.stats.increment "hook.fail.timeout.#{hook_name}" - raise - rescue - Service.stats.increment "hook.fail.exception.#{hook_name}" - raise + def load_services + require File.expand_path("../services/http_post", __FILE__) + path = File.expand_path("../services/**/*.rb", __FILE__) + Dir[path].each { |lib| require(lib) } end # Tracks the defined services. @@ -105,10 +130,17 @@ def default_events(*events) if events.empty? @default_events ||= [:push] else - @default_events = events + @default_events = events.flatten end end + # Gets a list of events support by the service. Should be a superset of + # default_events. + def supported_events + return ALL_EVENTS.dup if method_defined? :receive_event + ALL_EVENTS.select { |event| method_defined? "receive_#{event}" } + end + # Gets the current schema for the data attributes that this Service # expects. This schema is used to generate the GitHub repository admin # interface. The attribute types loosely to HTML input elements. @@ -122,7 +154,7 @@ def default_events(*events) # FooService.schema # # => [[:string, :token]] # - # Returns an Array of [Symbol, String] tuples. + # Returns an Array of [Symbol attribute type, Symbol attribute name] tuples. def schema @schema ||= [] end @@ -184,6 +216,21 @@ def boolean(*attrs) add_to_schema :boolean, attrs end + # Public: get a list of attributes that are approved for logging. Don't + # add things like tokens or passwords here. + # + # Returns an Array of String attribute names. + def white_listed + @white_listed ||= [] + end + + def white_list(*attrs) + attrs.each do |attr| + white_listed << attr.to_s + end + end + + # Adds the given attributes to the Service's data schema. # # type - A Symbol specifying the type: :string, :password, :boolean. @@ -200,11 +247,15 @@ def add_to_schema(type, attrs) # user-facing documentation regarding the Service. # # Returns a String. - def title - @title ||= begin - hook = name.dup - hook.sub! /.*:/, '' - hook + def title(value = nil) + if value + @title = value + else + @title ||= begin + hook = name.dup + hook.sub! /.*:/, '' + hook + end end end @@ -219,12 +270,61 @@ def title # short string that is used to uniquely identify the service internally. # # Returns a String. - def hook_name - @hook_name ||= begin - hook = name.dup - hook.downcase! - hook.sub! /.*:/, '' - hook + def hook_name(value = nil) + if value + @hook_name = value + else + @hook_name ||= begin + hook = name.dup + hook.downcase! + hook.sub! /.*:/, '' + hook + end + end + end + + # Sets the uniquely identifying name for this Service type. + # + # hook_name - The String name. + # + # Returns a String. + attr_writer :hook_name + + attr_reader :url, :logo_url + + def url(value = nil) + if value + @url = value + else + @url + end + end + + def logo_url(value = nil) + if value + @logo_url = value + else + @logo_url + end + end + + def supporters + @supporters ||= [] + end + + def maintainers + @maintainers ||= [] + end + + def supported_by(values) + values.each do |contributor_type, value| + supporters.push(*Contributor.create(contributor_type, value)) + end + end + + def maintained_by(values) + values.each do |contributor_type, value| + maintainers.push(*Contributor.create(contributor_type, value)) end end @@ -233,8 +333,16 @@ def hook_name # # Returns a Hash. def secrets - @secrets ||= - (File.exist?(secret_file) && YAML.load_file(secret_file)) || {} + @secrets ||= begin + jabber = ENV['SERVICES_JABBER'].to_s.split("::") + twitter = ENV['SERVICES_TWITTER'].to_s.split("::") + + { 'jabber' => {'user' => jabber[0], 'password' => jabber[1] }, + 'boxcar' => {'apikey' => ENV['SERVICES_BOXCAR'].to_s}, + 'twitter' => {'key' => twitter[0], 'secret' => twitter[1]}, + 'bitly' => {'key' => ENV['SERVICES_BITLY'].to_s} + } + end end # Public: Gets the Hash of email configuration options. These are set on @@ -242,9 +350,21 @@ def secrets # # Returns a Hash. def email_config - @email_config ||= - (File.exist?(email_config_file) && YAML.load_file(email_config_file)) || {} + @email_config ||= begin + hash = (File.exist?(email_config_file) && YAML.load_file(email_config_file)) || {} + EMAIL_KEYS.each do |key| + env_key = "EMAIL_SMTP_#{key.upcase}" + value = ENV[env_key] + if value && value != '' + hash[key] = value + end + end + hash + end end + EMAIL_KEYS = %w(address port domain authentication user_name password + enable_starttls_auto openssl_verify_mode enable_logging + noreply_address) # Gets the path to the secret configuration file. # @@ -261,10 +381,11 @@ def email_config_file end def objectify(hash) + struct = OpenStruct.new hash.each do |key, value| - hash[key] = objectify(value) if value.is_a?(Hash) + struct.send("#{key}=", value.is_a?(Hash) ? objectify(value) : value) end - OpenStruct.new hash + struct end # Sets the path to the secrets configuration file. @@ -300,7 +421,6 @@ def objectify(hash) # Returns nothing. def inherited(svc) Service.services << svc - Service::App.service(svc) super end end @@ -327,6 +447,9 @@ def inherited(svc) # Returns a Symbol. attr_reader :event + # Optional String unique identifier for this exact event. + attr_accessor :delivery_guid + # Sets the Faraday::Connection for this Service instance. # # http - New Faraday::Connection instance. @@ -355,6 +478,14 @@ def inherited(svc) # Returns nothing. attr_writer :ca_file + attr_reader :event_method + + attr_reader :http_calls + + attr_reader :remote_calls + + attr_reader :pre_delivery_callbacks + def initialize(event = :push, data = {}, payload = nil) helper_name = "#{event.to_s.classify}Helpers" if Service.const_defined?(helper_name) @@ -362,19 +493,38 @@ def initialize(event = :push, data = {}, payload = nil) extend @helper end - @event = event.to_sym - @data = data + @event = event.to_sym + @data = data || {} @payload = payload || sample_payload + @event_method = ["receive_#{event}", "receive_event"].detect do |method| + respond_to?(method) + end @http = @secrets = @email_config = nil + @http_calls = [] + @remote_calls = [] + @pre_delivery_callbacks = [] end - # Public: Shortens the given URL with bit.ly. + # Boolean fields as either nil, "0", or "1". + def config_boolean_true?(boolean_field) + data[boolean_field].to_i == 1 + end + + def config_boolean_false?(boolean_field) + !config_boolean_true?(boolean_field) + end + + def respond_to_event? + !@event_method.nil? + end + + # Public: Shortens the given URL with git.io. # # url - String URL to be shortened. # - # Returns the String URL response from bit.ly. + # Returns the String URL response from git.io. def shorten_url(url) - res = http_post("http://git.io", :url => url) + res = http_post("https://git.io", :url => url) if res.status == 201 res.headers['location'] else @@ -384,6 +534,8 @@ def shorten_url(url) url end + ENABLED_TRANSPORTS = ["", "http", "https"] + # Public: Makes an HTTP GET call. # # url - Optional String URL to request. @@ -413,11 +565,18 @@ def shorten_url(url) # # Yields a Faraday::Request instance. # Returns a Faraday::Response instance. - def http_get(url = nil, params = nil, headers = nil) + def http_get(url = nil, params = {}, headers = {}) + raise_config_error("Invalid scheme") unless permitted_transport?(url) + url = url.strip if url + + if pre_delivery_callbacks.any? + pre_delivery_callbacks.each { |c| c.call(url, nil, headers, params) } + end + http.get do |req| req.url(url) if url - req.params.update(params) if params - req.headers.update(headers) if headers + req.params.update(params) if params.present? + req.headers.update(headers) if headers.present? yield req if block_given? end end @@ -442,15 +601,15 @@ def http_get(url = nil, params = nil, headers = nil) # req.basic_auth("username", "password") # req.params[:page] = 1 # http://github.com/create?page=1 # req.headers['Content-Type'] = 'application/json' - # req.body = {:foo => :bar}.to_json + # req.body = generate_json(:foo => :bar) # end # # => # # Yields a Faraday::Request instance. # Returns a Faraday::Response instance. - def http_post(url = nil, body = nil, headers = nil) + def http_post(url = nil, body = nil, headers = {}, params = {}) block = Proc.new if block_given? - http_method :post, url, body, headers, &block + http_method :post, url, body, headers, params, &block end # Public: Makes an HTTP call. @@ -474,25 +633,38 @@ def http_post(url = nil, body = nil, headers = nil) # req.basic_auth("username", "password") # req.params[:page] = 1 # http://github.com/create?page=1 # req.headers['Content-Type'] = 'application/json' - # req.body = {:foo => :bar}.to_json + # req.body = generate_json(:foo => :bar) # end # # => # # Yields a Faraday::Request instance. # Returns a Faraday::Response instance. - def http_method(method, url = nil, body = nil, headers = nil) + def http_method(method, url = nil, body = nil, headers = {}, params = {}) block = Proc.new if block_given? + url = url.strip if url + raise_config_error("Invalid scheme") unless permitted_transport?(url) + + if pre_delivery_callbacks.any? + pre_delivery_callbacks.each { |c| c.call(url, body, headers, params) } + end + check_ssl do http.send(method) do |req| req.url(url) if url - req.headers.update(headers) if headers + req.headers.update(headers) if headers.present? req.body = body if body + req.params = params if params.present? block.call req if block end end end + def permitted_transport?(url = nil) + ENABLED_TRANSPORTS.include?(http.url_prefix.scheme.to_s.downcase) && + ENABLED_TRANSPORTS.include?(Addressable::URI.parse(url).scheme.to_s.downcase) + end + # Public: Lazily loads the Faraday::Connection for the current Service # instance. # @@ -501,20 +673,89 @@ def http_method(method, url = nil, body = nil, headers = nil) # Returns a Faraday::Connection instance. def http(options = {}) @http ||= begin - req = options[:request] ||= {} - req[:open_timeout] ||= 3 - req[:timeout] ||= 10 - ssl = options[:ssl] ||= {} - ssl[:ca_file] ||= ca_file - ssl[:verify_depth] ||= 5 + config = self.class.default_http_options + config.each do |key, sub_options| + next if key == :adapter + sub_hash = options[key] ||= {} + sub_options.each do |sub_key, sub_value| + sub_hash[sub_key] ||= sub_value + end + end + options[:ssl][:ca_file] ||= ca_file + adapter = Array(options.delete(:adapter) || config[:adapter]) Faraday.new(options) do |b| - b.request :url_encoded - b.adapter :net_http + b.request(:url_encoded) + b.adapter *adapter + b.use(HttpReporter, self) end end end + def self.default_http_options + @@default_http_options ||= { + :adapter => :net_http, + :request => {:timeout => 10, :open_timeout => 5}, + :ssl => {:verify_depth => 5}, + :headers => {} + } + end + + # Passes HTTP response debug data to the HTTP callbacks. + def receive_http(env) + @http_calls << env + end + + # Passes raw debug data to remote call callbacks. + def receive_remote_call(text) + @remote_calls << text + end + + def receive(timeout = nil) + return unless respond_to_event? + timeout_sec = (timeout || 20).to_i + Service::Timeout.timeout(timeout_sec, TimeoutError) do + send(event_method) + end + + self + rescue Service::ConfigurationError, Errno::EHOSTUNREACH, Errno::ECONNRESET, SocketError, Net::ProtocolError => err + if !err.is_a?(Service::Error) + err = ConfigurationError.new(err) + end + raise err + end + + def generate_json(body) + JSON.generate(clean_for_json(body)) + end + + def clean_hash_for_json(hash) + new_hash = {} + hash.keys.each do |key| + new_hash[key] = clean_for_json(hash[key]) + end + new_hash + end + + def clean_array_for_json(array) + array.map { |value| clean_for_json(value) } + end + + # overridden in Hookshot for proper UTF-8 transcoding with CharlockHolmes + def clean_string_for_json(str) + str.to_s.force_encoding(Service::UTF8) + end + + def clean_for_json(value) + case value + when Hash then clean_hash_for_json(value) + when Array then clean_array_for_json(value) + when String then clean_string_for_json(value) + else value + end + end + # Public: Checks for an SSL error, and re-raises a Services configuration error. # # Returns nothing. @@ -524,6 +765,47 @@ def check_ssl raise_config_error "Invalid SSL cert" end + # Public: Builds a log message for this Service request. Respects the white + # listed attributes in the Service schema. + # + # Returns a String. + def log_message(status = 0) + "[%s] %03d %s/%s %s" % [Time.now.utc.to_s(:db), status, + self.class.hook_name, @event, generate_json(log_data)] + end + + # Public: Builds a sanitized Hash of the Data hash without passwords. + # + # Returns a Hash. + def log_data + @log_data ||= self.class.white_listed.inject({}) do |hash, key| + if value = data[key] + hash.update key => sanitize_log_value(value) + else + hash + end + end + end + + # Attempts to sanitize passwords out of URI strings. + # + # value - The String attribute value. + # + # Returns a sanitized String. + def sanitize_log_value(value) + string = value.to_s + string.strip! + if string =~ /^[a-z]+\:\/\// + uri = Addressable::URI.parse(string) + uri.password = "*" * 8 if uri.password + uri.to_s + else + string + end + rescue Addressable::URI::InvalidURIError + string + end + # Public: Gets the Hash of secret configuration options. These are set on # the GitHub servers and never committed to git. # @@ -548,6 +830,10 @@ def raise_config_error(msg = "Invalid configuration") raise ConfigurationError, msg end + def raise_missing_error(msg = "Remote endpoint not found") + raise MissingError, msg + end + # Gets the path to the SSL Certificate Authority certs. These were taken # from: http://curl.haxx.se/ca/cacert.pem # @@ -563,6 +849,29 @@ def sample_payload @helper ? @helper.sample_payload : {} end + def reportable_http_env(env, time) + { + :request => { + :url => env[:url].to_s, + :headers => env[:request_headers] + }, :response => { + :status => env[:status], + :headers => env[:response_headers], + :body => env[:body].to_s, + :duration => "%.02fs" % [Time.now - time] + }, + :adapter => env[:adapter] + } + end + + def before_delivery(&block) + @pre_delivery_callbacks << block + end + + def reset_pre_delivery_callbacks! + @pre_delivery_callbacks = [] + end + # Raised when an unexpected error occurs during service hook execution. class Error < StandardError attr_reader :original_exception @@ -580,13 +889,27 @@ class TimeoutError < Timeout::Error # fail with this exception may be automatically disabled. class ConfigurationError < Error end + + class MissingError < Error + end + + class HttpReporter < Faraday::Response::Middleware + def initialize(app, service = nil) + super(app) + @service = service + @time = Time.now + end + + def on_complete(env) + @service.receive_http(@service.reportable_http_env(env, @time)) + end + end end +require 'timeout' begin require 'system_timer' Service::Timeout = SystemTimer rescue LoadError - require 'timeout' Service::Timeout = Timeout end - diff --git a/lib/service/events/commit_comment_helpers.rb b/lib/service/events/commit_comment_helpers.rb new file mode 100644 index 000000000..037e3840e --- /dev/null +++ b/lib/service/events/commit_comment_helpers.rb @@ -0,0 +1,36 @@ +module Service::CommitCommentHelpers + include Service::HelpersWithMeta + + def comment + @comment ||= self.class.objectify(payload['comment']) + end + + def summary_url + comment.html_url + end + + def summary_message + "[%s] %s comment on commit %s: %s. %s" % [ + repo.name, + sender.login, + comment.commit_id, + comment.body, + comment.html_url + ] + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def self.sample_payload + comment_id = 3332777 + commit_id = "441e5686a726b79bcdace639e2591a60718c9719" + Service::HelpersWithMeta.sample_payload.merge( + "comment" => { + "user" => { "login" => "defunkt" }, + "commit_id" => commit_id, + "body" => "this\r\nis\r\ntest comment", + "html_url" => "https://github.com/mojombo/magik/commit/#{commit_id}#commitcomment-#{comment_id}" + } + ) + end +end diff --git a/lib/service/events/deployment_helpers.rb b/lib/service/events/deployment_helpers.rb new file mode 100644 index 000000000..0f616a3f2 --- /dev/null +++ b/lib/service/events/deployment_helpers.rb @@ -0,0 +1,57 @@ +module Service::DeploymentHelpers + def self.sample_deployment_payload + { + "deployment" => { + "id"=>721, + "ref"=>"master", + "sha"=>"9be5c2b9c34c1f8beb0cec30bb0c875d098f45ef", + "name"=>"atmos/my-robot", + "task"=>"deploy", + "environment"=>"production", + "payload"=>{ + "config"=>{ + "heroku_production_name"=>"my-app" + } + }, + "description"=>nil, + }, + "repository"=> + { + "id"=>16650088, + "name"=>"my-robot", + "full_name"=>"atmos/my-robot", + "owner"=> + { + "login"=>"atmos", + "id"=>6626297, + "avatar_url"=> "https://identicons.github.com/86f5d368c1103c6a77ddb061e7727e46.png", + "gravatar_id"=>nil, + "url"=>"https://api.github.com/users/atmos", + "html_url"=>"https://github.com/atmos", + "type"=>"Organization", + "site_admin"=>false + }, + "private"=>true, + "html_url"=>"https://github.com/atmos/my-robot", + "description"=>"SlackHQ hubot for atmos", + "fork"=>false, + "created_at"=>"2014-02-08T18:40:20Z", + "updated_at"=>"2014-02-20T00:00:11Z", + "pushed_at"=>"2014-02-16T02:00:37Z", + "default_branch"=>"master", + "master_branch"=>"master" + }, + "sender"=> + { + "login"=>"atmos", + "id"=>38, + "avatar_url"=> "https://gravatar.com/avatar/a86224d72ce21cd9f5bee6784d4b06c7?d=https%3A%2F%2Fidenticons.github.com%2Fa5771bce93e200c36f7cd9dfd0e5deaa.png&r=x", + "gravatar_id"=>"a86224d72ce21cd9f5bee6784d4b06c7", + "url"=>"https://api.github.com/users/atmos", + "html_url"=>"https://github.com/atmos", + "type"=>"User", + "site_admin"=>true + } + } + end +end diff --git a/lib/service/events/gollum_helpers.rb b/lib/service/events/gollum_helpers.rb new file mode 100644 index 000000000..2a7d690d4 --- /dev/null +++ b/lib/service/events/gollum_helpers.rb @@ -0,0 +1,65 @@ +module Service::GollumHelpers + include Service::HelpersWithMeta + + def pages + payload['pages'] + end + + def summary_url + if pages.size == 1 + pages[0]['html_url'] + else + "#{payload['repository']['url']}/wiki" + end + end + + def summary_message + if pages.size == 1 + single_page_summary_message + else + pages_summary_message + end + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def single_page_summary_message + summary = pages[0]['summary'] + + '[%s] %s %s wiki page %s%s' % [ + repo.name, + sender.login, + pages[0]['action'], + pages[0]['title'], + summary ? ": #{summary}" : '', + ] + end + + def pages_summary_message + counts = {} + counts.default = 0 + pages.each { |page| counts[page['action']] += 1 } + + actions = [] + counts.each { |action, count| actions << "#{action} #{count}" } + + '[%s] %s %s wiki pages' % [ + repo.name, + sender.login, + actions.sort.to_sentence, + ] + end + + def self.sample_payload + Service::HelpersWithMeta.sample_payload.merge( + 'pages' => [{ + 'html_url' => 'https://github.com/mojombo/magik/wiki/Foo', + 'sha' => '0123456789abcdef0123456789abcdef01234567', + 'action' => 'created', + 'summary' => nil, + 'title' => 'Foo', + 'page_name' => 'Foo', + }] + ) + end +end diff --git a/lib/events/helpers/helpers_with_actions.rb b/lib/service/events/helpers/helpers_with_actions.rb similarity index 100% rename from lib/events/helpers/helpers_with_actions.rb rename to lib/service/events/helpers/helpers_with_actions.rb diff --git a/lib/service/events/helpers/helpers_with_meta.rb b/lib/service/events/helpers/helpers_with_meta.rb new file mode 100644 index 000000000..863268d7d --- /dev/null +++ b/lib/service/events/helpers/helpers_with_meta.rb @@ -0,0 +1,20 @@ +module Service::HelpersWithMeta + def repo + @repo ||= self.class.objectify(payload['repository']) + end + + def sender + @sender ||= self.class.objectify(payload['sender']) + end + + def self.sample_payload + { + "repository" => { + "name" => "grit", + "url" => "http://github.com/mojombo/grit", + "owner" => { "login" => "mojombo" } + }, + "sender" => { "login" => 'defunkt' } + } + end +end diff --git a/lib/service/events/issue_comment_helpers.rb b/lib/service/events/issue_comment_helpers.rb new file mode 100644 index 000000000..9d2089d60 --- /dev/null +++ b/lib/service/events/issue_comment_helpers.rb @@ -0,0 +1,45 @@ +module Service::IssueCommentHelpers + include Service::HelpersWithMeta, + Service::HelpersWithActions + + def issue + @issue ||= self.class.objectify(payload['issue']) + end + + def comment + @comment ||= self.class.objectify(payload['comment']) + end + + def summary_url + comment.html_url + end + + def summary_message + "[%s] %s comment on issue #%d: %s. %s" % [ + repo.name, + sender.login, + issue.number, + comment.body, + comment.html_url + ] + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def self.sample_payload + Service::HelpersWithMeta.sample_payload.merge( + "action" => "created", + "issue" => { + "number" => 5, + "state" => "open", + "title" => "booya", + "body" => "boom town", + "user" => { "login" => "mojombo" } + }, + "comment" => { + "user" => { "login" => "defunkt" }, + "body" => "this\r\nis\r\ntest comment" + } + ) + end +end diff --git a/lib/events/issue_helpers.rb b/lib/service/events/issue_helpers.rb similarity index 64% rename from lib/events/issue_helpers.rb rename to lib/service/events/issue_helpers.rb index bc9a01ece..49c283148 100644 --- a/lib/events/issue_helpers.rb +++ b/lib/service/events/issue_helpers.rb @@ -1,5 +1,5 @@ module Service::IssueHelpers - include Service::HelpersWithRepo, + include Service::HelpersWithMeta, Service::HelpersWithActions def issue @@ -11,18 +11,18 @@ def summary_url end def summary_message - "[%s] %s opened issue #%d: %s. %s" % [ + "[%s] %s %s issue #%d: %s" % [ repo.name, - issue.user.login, + sender.login, + action, issue.number, - issue.title, - issue.html_url] + issue.title] rescue raise_config_error "Unable to build message: #{$!.to_s}" end def self.sample_payload - { + Service::HelpersWithMeta.sample_payload.merge( "action" => "opened", "issue" => { "number" => 5, @@ -30,12 +30,7 @@ def self.sample_payload "title" => "booya", "body" => "boom town", "user" => { "login" => "mojombo" } - }, - "repository" => { - "name" => "grit", - "url" => "http://github.com/mojombo/grit", - "owner" => { "login" => "mojombo" } } - } + ) end end diff --git a/lib/service/events/public_helpers.rb b/lib/service/events/public_helpers.rb new file mode 100644 index 000000000..536a8dce7 --- /dev/null +++ b/lib/service/events/public_helpers.rb @@ -0,0 +1,20 @@ +module Service::PublicHelpers + include Service::HelpersWithMeta + + def summary_url + payload['repository']['url'] + end + + def summary_message + "[%s] %s made the repository public" % [ + repo.name, + sender.login, + ] + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def self.sample_payload + Service::HelpersWithMeta.sample_payload + end +end diff --git a/lib/events/pull_request_helpers.rb b/lib/service/events/pull_request_helpers.rb similarity index 52% rename from lib/events/pull_request_helpers.rb rename to lib/service/events/pull_request_helpers.rb index b219c59eb..3edc9948c 100644 --- a/lib/events/pull_request_helpers.rb +++ b/lib/service/events/pull_request_helpers.rb @@ -1,5 +1,5 @@ module Service::PullRequestHelpers - include Service::HelpersWithRepo, + include Service::HelpersWithMeta, Service::HelpersWithActions def pull @@ -14,37 +14,37 @@ def summary_message base_ref = pull.base.label.split(':').last head_ref = pull.head.label.split(':').last - "[%s] %s opened pull request #%d: %s (%s...%s) %s" % [ + "[%s] %s %s pull request #%d: %s (%s...%s)" % [ repo.name, - pull.user.login, + sender.login, + action, pull.number, pull.title, base_ref, - head_ref != base_ref ? head_ref : pull.head.label, - pull.html_url] + head_ref != base_ref ? head_ref : pull.head.label] rescue raise_config_error "Unable to build message: #{$!.to_s}" end def self.sample_payload - { + repo_owner = "mojombo" + repo_name = "magik" + pull_user = "foo" + pull_number = 5 + Service::HelpersWithMeta.sample_payload.merge( "action" => "opened", "pull_request" => { - "number" => 5, + "number" => pull_number, "commits" => 1, "state" => "open", "title" => "booya", "body" => "boom town", - "user" => { "login" => "mojombo" }, - "head" => {"label" => "foo:feature"}, - "base" => {"label" => "mojombo:master"} - }, - "repository" => { - "name" => "grit", - "url" => "http://github.com/mojombo/grit", - "owner" => { "login" => "mojombo" } + "user" => { "login" => "#{pull_user}" }, + "head" => {"label" => "#{pull_user}:feature"}, + "base" => {"label" => "#{repo_owner}:master"}, + "html_url" => "https://github.com/#{repo_owner}/#{repo_name}/pulls/#{pull_number}" } - } + ) end end diff --git a/lib/service/events/pull_request_review_comment_helpers.rb b/lib/service/events/pull_request_review_comment_helpers.rb new file mode 100644 index 000000000..1e7a14c9f --- /dev/null +++ b/lib/service/events/pull_request_review_comment_helpers.rb @@ -0,0 +1,62 @@ +module Service::PullRequestReviewCommentHelpers + include Service::HelpersWithMeta + + def comment + @comment ||= self.class.objectify(payload['comment']) + end + + def summary_url + comment.html_url + end + + def pull_request_number + comment.pull_request_url =~ /\/(\d+)$/ + $1 + end + + def summary_message + "[%s] %s comment on pull request #%d %s: %s. %s" % [ + repo.name, + sender.login, + pull_request_number, + comment.commit_id, + comment.body, + comment.html_url + ] + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def self.sample_payload + repo_owner = "mojombo" + repo_name = "magik" + pull_user = "foo" + pull_number = 5 + comment_id = 18785396 + commit_id = "03af7b9daa89ea2821116adaabf78620a14346a0" + + Service::HelpersWithMeta.sample_payload.merge( + "comment" => { + "url" => "https://api.github.com/repos/#{repo_owner}/#{repo_name}/pulls/comments/#{comment_id}", + "id" => comment_id, + "user" => { "login" => "defunkt", }, + "body" => "very\r\ncool", + "commit_id" => commit_id, + "original_commit_id" => commit_id, + "html_url" => "https://github.com/#{repo_owner}/#{repo_name}/pull/#{pull_number}#discussion_r#{comment_id}", + "pull_request_url" => "https://api.github.com/repos/#{repo_owner}/#{repo_name}/pulls/#{pull_number}", + "_links" => { + "self" => { + "href" => "https://api.github.com/repos/#{repo_owner}/#{repo_name}/pulls/comments/#{comment_id}" + }, + "html" => { + "href" => "https://github.com/#{repo_owner}/#{repo_name}/pull/#{pull_number}#discussion_r#{comment_id}" + }, + "pull_request" => { + "href" => "https://api.github.com/#{repo_owner}/#{repo_name}/test/pulls/#{pull_number}" + } + } + } + ) + end +end diff --git a/lib/events/push_helpers.rb b/lib/service/events/push_helpers.rb similarity index 98% rename from lib/events/push_helpers.rb rename to lib/service/events/push_helpers.rb index bb5e8942f..f6ebb7eb1 100644 --- a/lib/events/push_helpers.rb +++ b/lib/service/events/push_helpers.rb @@ -168,10 +168,6 @@ def distinct_commits end) end - def receive - receive_push - end - def self.sample_payload { "after" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", @@ -182,7 +178,10 @@ def self.sample_payload "repository" => { "name" => "grit", "url" => "http://github.com/mojombo/grit", - "owner" => { "name" => "mojombo", "email" => "tom@mojombo.com" } + "owner" => { "name" => "mojombo", "email" => "tom@mojombo.com" }, + "master_branch" => "master", + "default_branch" => "master", + "private" => false }, "pusher" => { diff --git a/lib/service/events/status_helpers.rb b/lib/service/events/status_helpers.rb new file mode 100644 index 000000000..889ebd8d7 --- /dev/null +++ b/lib/service/events/status_helpers.rb @@ -0,0 +1,108 @@ +module Service::StatusHelpers + def self.sample_status_payload + {"sha"=>"7b80eb100206a56523dbda6202d8e5daa05e265b", + "name"=>"mojombo/grit", + "target_url"=>nil, + "context"=>"default", + "description"=>nil, + "state"=>"success", + "branches"=> [ + { + "name"=>"master", + "commit"=> { + "sha"=>"69a8b72e2d3d955075d47f03d902929dcaf74033", + "url"=> "https://api.github.com/repos/mojombo/grit/commits/69a8b72e2d3d955075d47f03d902929dcaf74033" + } + }, + { + "name"=>"changes", + "commit"=> { + "sha"=>"05c588ba8cd510ecbe112d020f215facb17817a6", + "url"=> "https://api.github.com/repos/mojombo/grit/commits/05c588ba8cd510ecbe112d020f215facb17817a6" + } + }, + { + "name"=>"gh-pages", + "commit"=> { + "sha"=> "993b46bdfc03ae59434816829162829e67c4d490", + "url"=> "https://api.github.com/repos/mojombo/grit/commits/993b46bdfc03ae59434816829162829e67c4d490" + } + } + ], + "commit"=> { + "sha"=>"7b80eb100206a56523dbda6202d8e5daa05e265b", + "commit" => { + "author" => { + "name" =>"rtomayko", + "email" =>"rtomayko@users.noreply.github.com", + "date" =>"2014-05-20T22:26:15Z" + }, + "committer" => { + "name"=>"rtomayko", + "email"=>"rtomayko@users.noreply.github.com", + "date"=>"2014-05-20T22:26:15Z" + }, + "message"=>"Create README.md", + "tree"=> { + "sha"=>"aa81d3d185d48ac4eb935b57d9aa54e8eb0dcd9d", + "url"=> "https://api.github.com/repos/mojombo/grit/git/trees/aa81d3d185d48ac4eb935b57d9aa54e8eb0dcd9d" + }, + "url"=> "https://api.github.com/repos/mojombo/grit/git/commits/7b80eb100206a56523dbda6202d8e5daa05e265b", + "comment_count"=>23 + }, + "url"=> "https://api.github.com/repos/mojombo/grit/commits/7b80eb100206a56523dbda6202d8e5daa05e265b", + "html_url"=> "https://github.com/mojombo/grit/commit/7b80eb100206a56523dbda6202d8e5daa05e265b", + "comments_url"=> "https://api.github.com/repos/mojombo/grit/commits/7b80eb100206a56523dbda6202d8e5daa05e265b/comments", + "author"=> { + "login"=>"rtomayko", + "id"=>6752317, + "avatar_url"=>"https://avatars.githubusercontent.com/u/6752317?", + "gravatar_id"=>"258ae60b5512c8402b93673b7478d9c6", + "url"=>"https://api.github.com/users/rtomayko", + "type"=>"User", + "site_admin"=>false + }, + "committer"=> { + "login"=>"rtomayko", + "id"=>6752317, + "avatar_url"=>"https://avatars.githubusercontent.com/u/6752317?", + "gravatar_id"=>"258ae60b5512c8402b93673b7478d9c6", + "url"=>"https://api.github.com/users/rtomayko", + "type"=>"User", + "site_admin"=>false + }, + "parents"=>[] + }, + "repository"=> { + "id"=>20000106, + "name"=>"grit", + "full_name"=>"mojombo/grit", + "owner"=> { + "login"=>"rtomayko", + "id"=>6752317, + "avatar_url"=>"https://avatars.githubusercontent.com/u/6752317?", + "gravatar_id"=>"258ae60b5512c8402b93673b7478d9c6", + "url"=>"https://api.github.com/users/rtomayko", + "type"=>"User", + "site_admin"=>false + }, + "private"=>false, + "html_url"=>"https://github.com/mojombo/grit", + "description"=>"", + "fork"=>false, + "url"=>"https://api.github.com/repos/mojombo/grit", + "default_branch"=>"master" + }, + "sender"=> { + "login"=>"rtomayko", + "id"=>6752317, + "avatar_url"=>"https://avatars.githubusercontent.com/u/6752317?", + "gravatar_id"=>"258ae60b5512c8402b93673b7478d9c6", + "url"=>"https://api.github.com/users/rtomayko", + "received_events_url"=> "https://api.github.com/users/rtomayko/received_events", + "type"=>"User", + "site_admin"=>false + } + } + end +end diff --git a/lib/service/http_helper.rb b/lib/service/http_helper.rb new file mode 100644 index 000000000..ce8024bec --- /dev/null +++ b/lib/service/http_helper.rb @@ -0,0 +1,98 @@ +class Service + module HttpHelper + HMAC_DIGEST = OpenSSL::Digest.new('sha1') + + def deliver(url_value, options = {}) + insecure = options[:insecure_ssl] + ctype = options[:content_type] + secret = options[:secret] + + wrap_http_errors do + url = set_url(url_value) + + if insecure + http.ssl[:verify] = false + end + + body = encode_body(ctype) + + set_body_signature(body, secret) + + http_post url, body + end + end + + # Grabs a sanitized configuration value. + def config_value(key) + value = data[key.to_s].to_s + value.strip! + value + end + + # Grabs a sanitized configuration value and ensures it is set. + def required_config_value(key) + if (value = config_value(key)).empty? + raise_config_error("#{key.inspect} is empty") + end + + value + end + + def wrap_http_errors + yield + rescue Addressable::URI::InvalidURIError, Errno::EHOSTUNREACH + raise_missing_error $!.to_s + rescue SocketError + if $!.to_s =~ /getaddrinfo:/ + raise_missing_error "Invalid host name." + else + raise + end + rescue EOFError + raise_config_error "Invalid server response. Make sure the URL uses the correct protocol." + end + + def set_url(url) + url = url.to_s + url.gsub! /\s/, '' + + if url.empty? + raise_config_error "Invalid URL: #{url.inspect}" + end + + if url !~ /^https?\:\/\// + url = "http://#{url}" + end + + # set this so that basic auth is added, + # and GET params are added to the POST body + http.url_prefix = url + + url + end + + def set_body_signature(body, secret) + return if (secret = secret.to_s).empty? + http.headers['X-Hub-Signature'] = + 'sha1='+OpenSSL::HMAC.hexdigest(HMAC_DIGEST, secret, body) + end + + def original_body + raise NotImplementedError + end + + def encode_body(content_type = nil) + method = "encode_body_as_#{content_type}" + respond_to?(method) ? send(method) : default_encode_body + end + + def default_encode_body + encode_body_as_json + end + + def encode_body_as_json + http.headers['content-type'] = 'application/json' + generate_json(original_body) + end + end +end diff --git a/lib/service/structs.rb b/lib/service/structs.rb new file mode 100644 index 000000000..66a1b7914 --- /dev/null +++ b/lib/service/structs.rb @@ -0,0 +1,36 @@ +class Service + module StructLoading + def from(hash) + new *members.map { |attr| hash[attr.to_s] } + end + end + + class Meta < Struct.new(:id, :sender, :repository) + def self.from(hash) + sender = User.from(hash['sender']) + repository = Repository.from(hash['repository']) + repository.owner = User.from(hash['user']) + new hash['id'], sender, repository + end + end + + class User < Struct.new(:id, :login, :gravatar_id) + extend StructLoading + + def url + "https://github.com/#{login}" + end + end + + class Repository < Struct.new(:id, :source_id, :name, :owner) + extend StructLoading + + def name_with_owner + @name_with_owner ||= "#{owner.login}/#{name}" + end + + def url + owner.url << "/#{name}" + end + end +end diff --git a/services/README.md b/lib/services/README.md similarity index 93% rename from services/README.md rename to lib/services/README.md index c3bd45e80..8bdeeb57e 100644 --- a/services/README.md +++ b/lib/services/README.md @@ -13,8 +13,7 @@ end Inside the method, you can access the configuration data in a hash named `data`, and the payload data in a Hash named `payload`. -Note: A service can respond to more than one event. Currently, only `push` -is supported. +Note: A service can respond to more than one event. ## Tip: Check configuration data early. @@ -52,7 +51,7 @@ class Service::MyService < Service end ``` -## Tip: Test your service like a bossk. +## Tip: Test your service like a boss. ```ruby class MyServiceTest < Service::TestCase diff --git a/services/active_collab.rb b/lib/services/active_collab.rb similarity index 74% rename from services/active_collab.rb rename to lib/services/active_collab.rb index 35b62c738..64f4ebe21 100644 --- a/services/active_collab.rb +++ b/lib/services/active_collab.rb @@ -8,7 +8,9 @@ require 'uri' class Service::ActiveCollab < Service - string :url, :token, :project_id, :milestone_id, :category_id + string :url, :project_id, :milestone_id, :category_id + password :token + white_list :url, :project_id, :milestone_id, :category_id def receive_push if data['url'].to_s.empty? @@ -34,14 +36,15 @@ def receive_push build_message = statuses * "\n" - http.url_prefix = data['url'] - http.headers['Accept'] = 'application/xml' - - http.post do |req| - req.params['path_info'] = "projects/#{data['project_id']}/discussions/add" - req.params['token'] = data['token'] - req.body = params(push_message, build_message) - end + url = data['url'] + body = params(push_message, build_message) + headers = {:Accept => 'application/xml'} + params = { + :path_info => "projects/#{data['project_id']}/discussions/add", + :token => data['token'] + } + + http_post url, body, headers, params end def params(name, message) diff --git a/services/acunote.rb b/lib/services/acunote.rb similarity index 78% rename from services/acunote.rb rename to lib/services/acunote.rb index 843766271..de8df4b6a 100644 --- a/services/acunote.rb +++ b/lib/services/acunote.rb @@ -1,10 +1,10 @@ class Service::Acunote < Service - string :token + password :token def receive_push res = http_post "https://www.acunote.com/source_control/github/%s" % [ data['token'] ], - {'payload' => payload.to_json} + {'payload' => generate_json(payload)} if res.status != 200 raise_config_error diff --git a/lib/services/amazon_sns.rb b/lib/services/amazon_sns.rb new file mode 100644 index 000000000..90a39c737 --- /dev/null +++ b/lib/services/amazon_sns.rb @@ -0,0 +1,143 @@ +require 'aws-sdk-core' +require 'digest' + +class Service::AmazonSNS < Service + self.title = "Amazon SNS" + + string :aws_key, :sns_topic, :sns_region + + password :aws_secret + + white_list :aws_key, :sns_topic, :sns_region + + url "http://aws.amazon.com/console" + + maintained_by :github => "davidkelley" + + # Manage an event. Validate the data that has been received + # and then publish to SNS. + # + # Returns nothing. + def receive_event + validate_data + publish_to_sns(data, generate_json(payload)) + end + + # Maximum SNS message size is 256 kilobytes. + MAX_MESSAGE_SIZE = 256 * 1024 + + # Size in bytes for each partial message. + PARTIAL_MESSAGE_SIZE = 128 * 1024 + + # Create a new SNS object using the AWS Ruby SDK and publish to it. + # If the message exceeds the maximum SNS size limit of 256K, then it will be + # sent in parts. + # cfg - Configuration hash of key, secret, etc. + # json - The valid JSON payload to send. + # + # Returns the instantiated Amazon SNS Object + def publish_to_sns(cfg, json) + if json.bytesize <= MAX_MESSAGE_SIZE + return publish_messages_to_sns(cfg, [json]) + end + + checksum = Digest::MD5.hexdigest(json) + payloads = split_bytes(json, PARTIAL_MESSAGE_SIZE) + + messages = payloads.each_with_index.map do |payload, i| + generate_json({ + :error => "Message exceeds the maximum SNS size limit of 256K", + :page_total => payloads.length, + :page_number => i + 1, + :checksum => checksum, + :message => payload + }) + end + + publish_messages_to_sns(cfg, messages) + end + + # Build a valid AWS Configuration hash using the supplied + # parameters. + # + # Returns a valid AWS Config Hash. + def config(key, secret) + { + access_key_id: key, + secret_access_key: secret, + } + end + + # Build a valid set of message attributes for this message. + # + # Returns a valid Hash of message attributes. + def message_attributes + { + "X-Github-Event" => { + :data_type => "String", + :string_value => event.to_s + } + } + end + + # Validate the data that has been passed to the event. + # An AWS Key & Secret are required. As well as the ARN of an SNS topic. + # Defaults region to us-east-1 if not set. + # + # Returns nothing + def validate_data + if data['aws_key'].to_s.empty? || data['aws_secret'].to_s.empty? + raise_config_error "You need to provide an AWS Key and Secret Access Key" + end + + if data['sns_topic'].to_s.empty? || !data['sns_topic'].downcase[0..3] == 'arn' + raise_config_error "You need to provide a full SNS Topic ARN" + end + + data['sns_region'] = "us-east-1" if data['sns_region'].to_s.empty? + end + + private + + # Split the string into chunks of n bytes. + # + # Returns an array of strings. + def split_bytes(string, n) + string.bytes.each_slice(n).collect { |bytes| bytes.pack("C*") } + end + + # Create a new SNS object using the AWS Ruby SDK and publish it. + # cfg - Configuration hash of key, secret, etc. + # messages - The valid JSON messages to send. + # + # Returns the instantiated Amazon SNS Object. + def publish_messages_to_sns(cfg, messages) + begin + sns = Aws::SNS::Client.new({ + :region => cfg['sns_region'], + :access_key_id => cfg['aws_key'], + :secret_access_key => cfg['aws_secret'] + }) + + messages.each_with_index do |message, i| + puts "message ##{i} is #{message.bytesize} bytes" + sns.publish({ + :message => message, + :topic_arn => cfg['sns_topic'], + :message_attributes => message_attributes + }) + puts "finished publishing message ##{i}" + end + + sns + + rescue Aws::SNS::Errors::AuthorizationErrorException => e + raise_config_error e.message + rescue Aws::SNS::Errors::NotFoundException => e + raise_missing_error e.message + rescue SocketError + raise_missing_error + end + end + +end diff --git a/lib/services/apiary.rb b/lib/services/apiary.rb new file mode 100644 index 000000000..051ebd719 --- /dev/null +++ b/lib/services/apiary.rb @@ -0,0 +1,33 @@ +class Service::Apiary < Service::HttpPost + string :branch, :domain + white_list :branch + default_events :push + + url "http://apiary.io" + logo_url "http://static.apiary.io/css/design2/apiary-io-symbol-1x.png" + maintained_by :github => 'tu1ly' + supported_by :web => 'http://support.apiary.io/', + :email => 'support@apiary.io' + + APIARY_URL = "http://api.apiary.io/github/service-hook" + + def make_apiary_call + return true if not domain + http_post APIARY_URL, + :payload => generate_json(payload), + :branch => branch, + :vanity => domain + end + + def branch + @branch ||= (not data['branch'].to_s.strip.empty?) ? data['branch'].to_s.strip : 'master' + end + + def domain + @domain ||= (not data['domain'].to_s.strip.empty?) ? data['domain'].to_s.strip : nil + end + + def receive_event + return make_apiary_call + end +end diff --git a/services/appharbor.rb b/lib/services/appharbor.rb similarity index 69% rename from services/appharbor.rb rename to lib/services/appharbor.rb index 7fba27c56..eccab97cf 100644 --- a/services/appharbor.rb +++ b/lib/services/appharbor.rb @@ -1,6 +1,8 @@ class Service::AppHarbor < Service - string :application_slug, :token - + string :application_slug + password :token + white_list :application_slug + def receive_push slugs = data['application_slug'] token = data['token'] @@ -18,7 +20,7 @@ def receive_push def post_appharbor_message(slug, token) return unless commit = distinct_commits.last - create_build_url = "https://appharbor.com/application/#{slug}/build?authorization=#{token}" + create_build_url = "https://appharbor.com/applications/#{slug}/builds" appharbor_message = { :branches => { @@ -30,6 +32,9 @@ def post_appharbor_message(slug, token) } } - http_post create_build_url, appharbor_message.to_json, 'Accept' => 'application/json' + http.headers['Accept'] = 'application/json' + http.headers['Authorization'] = "BEARER #{token}" + + http_post create_build_url, generate_json(appharbor_message) end end diff --git a/lib/services/apropos.rb b/lib/services/apropos.rb new file mode 100644 index 000000000..4ba3f0ccf --- /dev/null +++ b/lib/services/apropos.rb @@ -0,0 +1,41 @@ + +class Service::Apropos < Service::HttpPost + default_events :commit_comment, :issues, :issue_comment, :pull_request, :push + string :project_id + + def apropos_url + proj_id = appid = required_config_value('project_id') + "http://www.apropos.io/api/v1/githook/#{proj_id}" + end + + def receive_commit_comment + http.headers['content-type'] = 'application/json' + http.headers['X-Github-Event'] = 'commit_comment' + deliver apropos_url + end + + def receive_issue_comment + http.headers['content-type'] = 'application/json' + http.headers['X-Github-Event'] = 'issue_comment' + deliver apropos_url + end + + def receive_pull_request + http.headers['content-type'] = 'application/json' + http.headers['X-Github-Event'] = 'pull_request' + deliver apropos_url + end + + def receive_issues + http.headers['content-type'] = 'application/json' + http.headers['X-Github-Event'] = 'issues' + deliver apropos_url + end + + def receive_push + http.headers['content-type'] = 'application/json' + http.headers['X-Github-Event'] = 'push' + deliver apropos_url + end + +end diff --git a/lib/services/asana.rb b/lib/services/asana.rb new file mode 100644 index 000000000..0ab2986cf --- /dev/null +++ b/lib/services/asana.rb @@ -0,0 +1,67 @@ +class Service::Asana < Service + password :auth_token + string :restrict_to_branch + boolean :restrict_to_last_commit + white_list :restrict_to_branch, :restrict_to_last_commit + + def receive_push + # make sure we have what we need + raise_config_error "Missing 'auth_token'" if data['auth_token'].to_s == '' + + user = payload['pusher']['name'] + branch = payload['ref'].split('/').last + + branch_restriction = data['restrict_to_branch'].to_s + commit_restriction = config_boolean_true?('restrict_to_last_commit') + + # check the branch restriction is poplulated and branch is not included + if branch_restriction.length > 0 && branch_restriction.index(branch) == nil + return + end + + rep = payload['repository']['url'].split('/').last(2).join('/') + push_msg = user + " pushed to branch " + branch + " of " + rep + + # code heavily derived from fog_bugz.rb + # iterate over commits + if commit_restriction + check_commit( payload['commits'].last, push_msg ) + else + payload['commits'].each do |commit| + check_commit( commit, push_msg ) + end + end + end + + def check_commit(commit, push_msg) + message = "(#{commit['url']})\n- #{commit['message']}" + + task_list = [] + message.split("\n").each do |line| + task_list.concat( line.scan(/#(\d+)/) ) + task_list.concat( line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/) ) + end + + # post commit to every taskid found + task_list.flatten.each do |taskid| + deliver_story taskid, "#{push_msg} #{message}" + end + end + + def deliver_story(task_id, text) + http.basic_auth(data['auth_token'], "") + http.headers['X-GitHub-Event'] = event.to_s + + res = http_post "https://app.asana.com/api/1.0/tasks/#{task_id}/stories", URI.encode_www_form("text" => text) + case res.status + when 200..299 + # Success + when 400 + # Unknown task. Could be GitHub issue or pull request number. Ignore it. + else + # Try to pull out an error message from the Asana response + error_message = JSON.parse(res.body)['errors'][0]['message'] rescue nil + raise_config_error(error_message || "Unexpected Error") + end + end +end diff --git a/lib/services/auto_deploy.rb b/lib/services/auto_deploy.rb new file mode 100644 index 000000000..d4cc8a116 --- /dev/null +++ b/lib/services/auto_deploy.rb @@ -0,0 +1,207 @@ +class Service::AutoDeploy < Service::HttpPost + password :github_token + string :environments + boolean :deploy_on_status + string :github_api_url + + white_list :environments, :deploy_on_status, :contexts, :github_api_url + + default_events :push, :status + + self.title = "GitHub Auto-Deployment" + url 'http://www.atmos.org/github-services/auto-deployment/' + logo_url 'https://camo.githubusercontent.com/edbc46e94fd4e9724da99bdd8da5d18e82f7b737/687474703a2f2f7777772e746f756368696e737069726174696f6e2e636f6d2f6173736574732f6865726f6b752d6c6f676f2d61663863386230333462346261343433613632376232393035666337316138362e706e67' + + maintained_by :github => 'atmos', :twitter => '@atmos' + + supported_by :web => 'https://github.com/contact', + :email => 'support@github.com', + :twitter => '@atmos' + + def github_repo_path + if payload['repository'] && payload['repository']['full_name'] + payload['repository']['full_name'] + else + [ payload['repository']['owner']['name'], + payload['repository']['name'] ].join('/') + end + end + + def environment_names + @environment_names ||= required_config_value("environments").split(',').map { |e| e.strip } + end + + def payload_ref + payload['ref'].to_s[/refs\/heads\/(.*)/, 1] + end + + def sha + if payload['after'] + payload['after'][0..7] + else + payload['sha'][0..7] + end + end + + def pusher_name + if payload['pusher'] + payload['pusher']['name'] + else + payload['commit']['committer']['login'] + end + end + + def default_branch + payload['repository']['default_branch'] + end + + def default_branch? + payload_ref == default_branch + end + + def deploy_on_push? + !deploy_on_status? + end + + def deploy_on_status? + config_boolean_true?('deploy_on_status') + end + + def version_string + payload_ref == sha ? sha : "#{payload_ref}@#{sha}" + end + + def receive_event + http.ssl[:verify] = true + + case event.to_sym + when :push + github_user_access? + github_repo_deployment_access? + deploy_from_push_payload if deploy_on_push? + when :status + github_user_access? + github_repo_deployment_access? + deploy_from_status_payload if deploy_on_status? + else + raise_config_error_with_message(:no_event_handler) + end + end + + def push_deployment_description + "Auto-Deployed on push by GitHub Services@#{Service.current_sha[0..7]} for #{pusher_name} - #{version_string}" + end + + def status_deployment_description + "Auto-Deployed on status by GitHub Services@#{Service.current_sha[0..7]} for #{pusher_name} - #{default_branch}@#{sha}" + end + + def deploy_from_push_payload + return unless default_branch? + + environment_names.each do |environment_name| + deployment_options = { + "ref" => sha, + "payload" => last_deployment_payload_for(environment_name), + "environment" => environment_name, + "description" => push_deployment_description, + "required_contexts" => [ ] + } + create_deployment_for_options(deployment_options) + end + end + + def status_payload_contains_default_branch? + payload['branches'].any? { |branch| branch['name'] == default_branch } + end + + def deploy_from_status_payload + return unless payload['state'] == 'success' + if status_payload_contains_default_branch? + environment_names.each do |environment_name| + deployment_options = { + "ref" => sha, + "payload" => last_deployment_payload_for(environment_name), + "environment" => environment_name, + "description" => status_deployment_description, + "required_contexts" => [ ] + } + create_deployment_for_options(deployment_options) + end + end + end + + def api_url + if config_value("github_api_url").empty? + "https://api.github.com" + else + config_value("github_api_url").chomp("/") + end + end + + def create_deployment_for_options(options) + deployment_path = "/repos/#{github_repo_path}/deployments" + response = http_post "#{api_url}#{deployment_path}" do |req| + req.headers.merge!(default_github_headers) + req.body = JSON.dump(options) + end + raise_config_error_with_message(:no_github_deployment_access) unless response.success? + end + + def last_deployment_payload_for(environment) + response = github_get("/repos/#{github_repo_path}/deployments") + unless response.success? + raise_config_error_with_message(:no_github_repo_deployment_access) + end + deployment = JSON.parse(response.body).find do |element| + element['environment'] == environment + end + deployment ? deployment['payload'] : { } + end + + def github_user_access? + response = github_get("/user") + unless response.success? + raise_config_error_with_message(:no_github_user_access) + end + end + + def github_repo_deployment_access? + response = github_get("/repos/#{github_repo_path}/deployments") + unless response.success? + raise_config_error_with_message(:no_github_repo_deployment_access) + end + end + + def github_get(path) + http_get "#{api_url}#{path}" do |req| + req.headers.merge!(default_github_headers) + end + end + + def default_github_headers + { + 'Accept' => "application/vnd.github.cannonball-preview+json", + 'User-Agent' => "Operation: California Auto-Deploy", + 'Content-Type' => "application/json", + 'Authorization' => "token #{required_config_value('github_token')}" + } + end + + def raise_config_error_with_message(sym) + raise_config_error(error_messages[sym]) + end + + def error_messages + @default_error_messages ||= { + :no_event_handler => + "The #{event} event is currently unsupported.", + :no_github_user_access => + "Unable to access GitHub with the provided token.", + :no_github_repo_deployment_access => + "Unable to access the #{github_repo_path} repository's deployments on GitHub with the provided token.", + :no_github_repo_deployment_status_access => + "Unable to update the deployment status on GitHub with the provided token." + } + end +end diff --git a/lib/services/aws_code_deploy.rb b/lib/services/aws_code_deploy.rb new file mode 100644 index 000000000..52940b28a --- /dev/null +++ b/lib/services/aws_code_deploy.rb @@ -0,0 +1,149 @@ +require 'aws-sdk-core' + +class Service::AwsCodeDeploy < Service::HttpPost + self.title = 'AWS CodeDeploy' + + string :application_name, :deployment_group, + :aws_access_key_id, :aws_region, :github_api_url + + password :aws_secret_access_key, :github_token + + white_list :application_name, + :deployment_group, + :github_api_url, + :aws_access_key_id, + :aws_region + + default_events :deployment + url "http://docs.aws.amazon.com/codedeploy/latest/APIReference/" + + def environment + payload['deployment']['environment'] + end + + def application_name + environment_application_name || required_config_value('application_name') + end + + def environment_application_name + codedeploy_payload_environment('application_name') + end + + def codedeploy_payload_environment(key) + codedeploy_payload && + codedeploy_payload[environment] && + codedeploy_payload[environment][key] + end + + def codedeploy_payload + payload['deployment']['payload'] && + payload['deployment']['payload']['config'] && + payload['deployment']['payload']['config']['codedeploy'] + end + + def receive_event + http.ssl[:verify] = true + + case event.to_s + when 'deployment' + deployment = create_deployment + update_deployment_statuses(deployment) + deployment + else + raise_config_error("The #{event} event is currently unsupported.") + end + end + + def create_deployment + options = { + :revision => { + :git_hub_location => { + :commit_id => payload['deployment']["sha"], + :repository => github_repo_path, + }, + :revision_type => "GitHub" + }, + + :application_name => application_name, + :deployment_group_name => environment + } + code_deploy_client.create_deployment(options) + end + + def update_deployment_statuses(deployment) + return unless config_value('github_token') && !config_value('github_token').empty? + + deployment_id = deployment['deployment_id'] + + deployment_status_options = { + "state" => "success", + "target_url" => aws_code_deploy_client_url, + "description" => "Deployment #{payload['deployment']['id']} Accepted by Amazon. (github-services@#{Service.current_sha[0..7]})" + } + + deployment_path = "/repos/#{github_repo_path}/deployments/#{payload['deployment']['id']}/statuses" + response = http_post "#{github_api_url}#{deployment_path}" do |req| + req.headers.merge!(default_github_headers) + req.body = JSON.dump(deployment_status_options) + end + raise_config_error("Unable to post deployment statuses back to the GitHub API.") unless response.success? + end + + def aws_code_deploy_client_url + "https://console.aws.amazon.com/codedeploy/home?region=#{custom_aws_region}#/deployments" + end + + def default_github_headers + { + 'Accept' => "application/vnd.github.cannonball-preview+json", + 'User-Agent' => "Operation: California", + 'Content-Type' => "application/json", + 'Authorization' => "token #{required_config_value('github_token')}" + } + end + + def github_repo_path + payload['repository']['full_name'] + end + + def code_deploy_aws_region + (codedeploy_payload && + codedeploy_payload["aws"] && + codedeploy_payload["aws"]["region"]) + end + + def custom_aws_region + return code_deploy_aws_region if code_deploy_aws_region + if config_value('aws_region').empty? + 'us-east-1' + else + config_value('aws_region') + end + end + + def stubbed_responses? + !!ENV['CODE_DEPLOY_STUB_RESPONSES'] + end + + def aws_config + { + :region => custom_aws_region, + :logger => stubbed_responses? ? nil : Logger.new(STDOUT), + :access_key_id => required_config_value("aws_access_key_id"), + :secret_access_key => required_config_value("aws_secret_access_key"), + :stub_responses => stubbed_responses? + } + end + + def code_deploy_client + @code_deploy_client ||= ::Aws::CodeDeploy::Client.new(aws_config) + end + + def github_api_url + if config_value("github_api_url").empty? + "https://api.github.com" + else + config_value("github_api_url") + end + end +end diff --git a/lib/services/aws_ops_works.rb b/lib/services/aws_ops_works.rb new file mode 100644 index 000000000..bf5007af9 --- /dev/null +++ b/lib/services/aws_ops_works.rb @@ -0,0 +1,160 @@ +require 'aws/ops_works' + +class Service::AwsOpsWorks < Service::HttpPost + self.title = 'AWS OpsWorks' + + string :app_id, # see AppId at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_App.html + :stack_id, # see StackId at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_Stack.html + :branch_name, # see Revision at http://docs.aws.amazon.com/opsworks/latest/APIReference/API_Source.html + :endpoint_region, # see AWS Opsworks Stacks at http://docs.aws.amazon.com/general/latest/gr/rande.html#opsworks_region + :github_api_url, # The GitHub API endpoint to post DeploymentStatus callbacks to + :aws_access_key_id # see AWSAccessKeyID at http://docs.aws.amazon.com/opsworks/latest/APIReference/CommonParameters.html + password :aws_secret_access_key, :github_token + + white_list :app_id, + :stack_id, + :branch_name, + :endpoint_region, + :github_api_url, + :aws_access_key_id + + default_events :push, :deployment + url "http://docs.aws.amazon.com/opsworks/latest/APIReference/API_CreateDeployment.html" + + def app_id + environment_app_id || required_config_value('app_id') + end + + def stack_id + environment_stack_id || required_config_value('stack_id') + end + + def deployment_payload + payload['deployment'] + end + + def deployment_command + (deployment_payload && deployment_payload['task']) || 'deploy' + end + + def environment_stack_id + opsworks_payload_environment('stack_id') + end + + def environment_app_id + opsworks_payload_environment('app_id') + end + + def opsworks_payload_environment(key) + opsworks_payload && opsworks_payload[environment] && opsworks_payload[environment][key] + end + + def opsworks_payload + deployment_payload && deployment_payload['payload'] && + deployment_payload['payload']['config'] && + deployment_payload['payload']['config']['opsworks'] + end + + def environment + deployment_payload['environment'] + end + + def receive_event + http.ssl[:verify] = true + + case event.to_s + when 'deployment' + update_app_revision(deployment_ref_name) + app_deployment = create_deployment + update_deployment_statuses(app_deployment) + app_deployment + when 'push' + if branch_name == required_config_value('branch_name') + create_deployment + end + else + raise_config_error("The #{event} event is currently unsupported.") + end + end + + def update_deployment_statuses(app_deployment) + return unless config_value('github_token') && !config_value('github_token').empty? + + deployment_id = app_deployment['deployment_id'] + + deployment_status_options = { + "state" => "success", + "target_url" => aws_opsworks_output_url, + "description" => "Deployment #{payload['deployment']['id']} Accepted by Amazon. (github-services@#{Service.current_sha[0..7]})" + } + + deployment_path = "/repos/#{github_repo_path}/deployments/#{payload['deployment']['id']}/statuses" + response = http_post "#{github_api_url}#{deployment_path}" do |req| + req.headers.merge!(default_github_headers) + req.body = JSON.dump(deployment_status_options) + end + raise_config_error("Unable to post deployment statuses back to the GitHub API.") unless response.success? + end + + def aws_opsworks_output_url + "https://console.aws.amazon.com/opsworks/home?#/stack/#{stack_id}/deployments" + end + + def default_github_headers + { + 'Accept' => "application/vnd.github.cannonball-preview+json", + 'User-Agent' => "Operation: California", + 'Content-Type' => "application/json", + 'Authorization' => "token #{required_config_value('github_token')}" + } + end + + def github_repo_path + payload['repository']['full_name'] + end + + def configured_branch_name + required_config_value('branch_name') + end + + def deployment_ref_name + payload['deployment']['ref'] + end + + def update_app_revision(revision_name) + app_source = { revision: revision_name } + if config_value('github_token') && !config_value('github_token').empty? + app_source = { + url: "#{github_api_url}/repos/#{github_repo_path}/zipball/#{revision_name}", + type: "archive", + username: required_config_value("github_token"), + password: "x-oauth-basic", + revision: revision_name + } + end + ops_works_client.update_app app_id: app_id, app_source: app_source + end + + def create_deployment + ops_works_client.create_deployment stack_id: stack_id, app_id: app_id, + command: { name: deployment_command } + end + + def ops_works_client + region = config_value('endpoint_region') + # The AWS library requires you pass `nil`, and not an empty string, if you + # want to connect to a legitimate default AWS host name. + region = nil if region.empty? + AWS::OpsWorks::Client.new access_key_id: required_config_value('aws_access_key_id'), + secret_access_key: required_config_value('aws_secret_access_key'), + region: region + end + + def github_api_url + if config_value("github_api_url").empty? + "https://api.github.com" + else + config_value("github_api_url") + end + end +end diff --git a/lib/services/backlog.rb b/lib/services/backlog.rb new file mode 100644 index 000000000..c0bc646d5 --- /dev/null +++ b/lib/services/backlog.rb @@ -0,0 +1,123 @@ +require 'uri' + +class Service::Backlog < Service + string :api_url, :user_id + password :password + white_list :space_id, :user_id + + def receive_push + if data['api_url'].to_s.empty? + raise_config_error "Backlog API URL not set" + end + if data['user_id'].to_s.empty? + raise_config_error "user_id not set" + end + if data['password'].to_s.empty? + raise_config_error "password not set" + end + + repository = payload['repository']['url'].to_s + commits = payload['commits'].collect{|c| Commit.new(c)} + issue_commits = sort_commits(commits) + issue_commits.sort.map do | issue, commits | + post(issue, repository, commits, branch.to_s) + end + + end + + def branch + return @branch if defined?(@branch) + + matches = payload['ref'].match(/^refs\/heads\/(.*)$/) + @branch = matches ? matches[1] : nil + end + + attr_writer :xmlrpc_client + def xmlrpc_client + @xmlrpc_client ||= begin + uri = URI(data['api_url']) + params = { + 'host' => uri.host, + 'path' => uri.path, + 'port' => uri.port, + 'user' => data['user_id'], + 'password' => data['password'], + 'use_ssl' => true + } + client = XMLRPC::Client.new3(params) + # call for auth check + client.call('backlog.getProjects') + client + rescue XMLRPC::FaultException + raise_config_error "Invalid login details" + rescue SocketError, RuntimeError, Errno::ECONNREFUSED + raise_config_error "Invalid server url" + end + end + + def sort_commits(commits) + issue_commits = Hash.new{|k,v| k[v] = []} + commits.each do |commit| + commit.issue.each do |issue| + issue_commits[issue] << commit + end + end + return issue_commits + end + + def post(issue, repository, commits, branch_name) + if commits.length == 0 + return + end + + branch_str = branch_name.empty? ? "" : "#{branch_name} at " + message = "pushed to #{branch_str}#{repository}\n\n" + + commits.each do |commit| + comment = "#{message}#{commit.comment}" + begin + if commit.status + xmlrpc_client.call('backlog.switchStatus', {'key' => issue, 'statusId' => commit.status, 'comment' => comment}) + else + xmlrpc_client.call('backlog.addComment', {'key' => issue, 'content' => comment}) + end + rescue XMLRPC::FaultException + raise_config_error "failed post" + rescue RuntimeError + raise_config_error "failed post" + end + end + end + + class Commit + attr_reader :status, :issue, :url, :id + + def initialize(commit_hash) + @id = commit_hash['id'].to_s + @url = commit_hash['url'].to_s + @message = commit_hash['message'].to_s + @status = nil + @issue = [] + + re_issue_key = /(?:\[\[)?(([A-Z0-9]+(?:_[A-Z0-9]+)*)-([1-9][0-9]*))(?:\]\])?/ + temp = @message + while temp =~ re_issue_key + issue << $1 + temp.sub!($1, '') + end + + re_status = /(?:^|\s+?)(#fixes|#fixed|#fix|#closes|#closed|#close)(?:\s+?|$)/ + while @message =~ re_status + switch = $1 + @message.sub!(switch, '') + @status = (switch =~ /fix/) ? 3 : 4 + end + end + + def comment() + output = "#{@url}\n" + output += @message.strip + return output + end + end +end diff --git a/services/bamboo.rb b/lib/services/bamboo.rb similarity index 59% rename from services/bamboo.rb rename to lib/services/bamboo.rb index 1a6c41838..49f79c6ac 100644 --- a/services/bamboo.rb +++ b/lib/services/bamboo.rb @@ -1,11 +1,12 @@ class Service::Bamboo < Service string :base_url, :build_key, :username password :password + white_list :base_url, :build_key, :username def receive_push verify_config branch = payload['ref'] - authenticated { |token| trigger_build(token, branch) } + trigger_build(branch) rescue SocketError => e if e.to_s =~ /getaddrinfo: Name or service not known/ raise_config_error("Invalid Bamboo host name") @@ -14,48 +15,46 @@ def receive_push end end - def trigger_build(token, ref) - commit_branch = ref.split('/').last + def trigger_build(ref) + # Post body is empty but Bamboo REST expects this to be set (in 3.x) + http.headers['Content-Type'] = 'application/xml' + + commit_branch = ref.sub(/\Arefs\/(heads|tags)\//, '') build_key.split(',').each do |branch_key| - #See if the split result is just a key or a branch:key + #See if the split result is just a key or a branch:key parts = branch_key.split(':') key = parts[0] if parts.length == 2 branch = parts[0] key = parts[1] - + #Has a branch, verify it matches the branch for the commit next unless branch == commit_branch end - - res = http_post "api/rest/executeBuild.action", - :auth => token, :buildKey => key - msg = XmlSimple.xml_in(res.body) - raise_config_error msg["error"] if msg["error"] - end - end - def authenticated - token = login - yield token - ensure - logout(token) + res = http_post "rest/api/latest/queue/#{key}" + handle_response(res) + end end - def login - res = http_post "api/rest/login.action", :username => username, :password => password - case res.status + def handle_response(response) + case response.status when 200..204 - XmlSimple.xml_in(res.body)['auth'].first + "Ok" when 403, 401, 422 then raise_config_error("Invalid credentials") when 404, 301 then raise_config_error("Invalid Bamboo project URL") - end - end - - def logout(token) - return unless token - http_post "api/rest/logout.action", "auth=#{CGI.escape(token)}" + else + maybe_xml = response.body + msg = if maybe_xml =~ / SERVICE_NAME, + :logo_url => LOGO_URL, + :creator_email_address => author_email, + :description => action, + :title => message, + :url => url + end + + def http_post_event(params) + http.basic_auth data['email_address'], data['password'] + http.headers['User-Agent'] = 'GitHub service hook' + http.headers['Content-Type'] = 'application/json' + http.headers['Accept'] = 'application/json' + + response = http_post(events_api_url, generate_json(params)) + + case response.status + when 401; raise_config_error "Invalid email + password: #{response.body.inspect}" + when 403; raise_config_error "No access to project: #{response.body.inspect}" + when 404; raise_config_error "No such project: #{response.body.inspect}" + when 422; raise_config_error "Validation error: #{response.body.inspect}" + end + end + + EVENTS_API_URL = 'https://basecamp.com:443/%d/api/v1/projects/%d/events.json' + def events_api_url + if data['project_url'] =~ %r{^https://basecamp\.com/(\d+)/projects/(\d+)} + EVENTS_API_URL % [$1, $2] + elsif data['project_url'] =~ /basecamphq\.com/ + raise_config_error "That's a URL for a Basecamp Classic project, not the new Basecamp. Check out the Basecamp Classic service hook instead!" + else + raise_config_error "That's not a URL to a Basecamp project! Navigate to the Basecamp project you'd like to post to and note the URL. It should look something like: https://basecamp.com/123456/projects/7890123 -- paste that URL here." + end + end +end diff --git a/lib/services/bugherd.rb b/lib/services/bugherd.rb new file mode 100644 index 000000000..4c7eee477 --- /dev/null +++ b/lib/services/bugherd.rb @@ -0,0 +1,13 @@ +class Service::BugHerd < Service + default_events :issues, :issue_comment, :push + string :project_key + white_list :project_key + + def receive_push + url = "http://www.bugherd.com/github_web_hook/#{data['project_key']}" + http_post url, :payload => generate_json(payload) + end + + alias receive_issues receive_push + alias receive_issue_comment receive_push +end diff --git a/services/bugzilla.rb b/lib/services/bugzilla.rb similarity index 83% rename from services/bugzilla.rb rename to lib/services/bugzilla.rb index 4b40e6a9b..1e6f7e0f5 100644 --- a/services/bugzilla.rb +++ b/lib/services/bugzilla.rb @@ -2,6 +2,7 @@ class Service::Bugzilla < Service string :server_url, :username, :integration_branch password :password boolean :central_repository + white_list :server_url, :username, :integration_branch def receive_push # Check for settings @@ -25,7 +26,7 @@ def receive_push bug_commits = sort_commits(commits) bugs_to_close = [] bug_commits.each_pair do | bug, commits | - if data['central_repository'] + if central_repository? # Only include first line of message if commit already mentioned commit_messages = commits.collect{|c| c.comment(bug_mentions_commit?(bug, c))} else @@ -39,11 +40,15 @@ def receive_push end # Close bugs - if data['central_repository'] + if central_repository? close_bugs(bugs_to_close) end end + def central_repository? + config_boolean_true?('central_repository') + end + # Name of the branch for this payload; nil if it isn't branch-related. def branch return @branch if defined?(@branch) @@ -61,7 +66,13 @@ def xmlrpc_client # XMLRPC client to communicate with Bugzilla server @xmlrpc_client ||= begin client = XMLRPC::Client.new2("#{data['server_url'].to_s}/xmlrpc.cgi") - client.call('User.login', {'login' => data['username'].to_s, 'password' => data['password'].to_s}) + + # Workaround for XMLRPC bug - https://bugs.ruby-lang.org/issues/8182 + # Should no longer be needed when we start running Ruby 2.2 + client.http_header_extra = {"accept-encoding" => "identity"} + + result = client.call('User.login', {'login' => data['username'].to_s, 'password' => data['password'].to_s}) + @token = result['token'] client rescue XMLRPC::FaultException raise_config_error "Invalid login details" @@ -70,6 +81,12 @@ def xmlrpc_client end end + def xmlrpc_authed_call(method, args) + # Add token parameter to XMLRPC call if one was received when logging into Bugzilla + args['Bugzilla_token'] = @token if not @token.nil? + xmlrpc_client.call(method, args) + end + def sort_commits(commits) # Sort commits into a hash of arrays based on bug id bug_commits = Hash.new{|k,v| k[v] = []} @@ -85,7 +102,7 @@ def bug_mentions_commit?(bug_id, commit) # Check if a bug already mentions a commit. # This is to avoid repeating commits that have # been pushed to another person's repository - result = xmlrpc_client.call('Bug.comments', {'ids' => [bug_id]}) + result = xmlrpc_authed_call('Bug.comments', {'ids' => [bug_id]}) all_comments = result['bugs']["#{bug_id}"]['comments'].collect{|c| c['text']}.join("\n") all_comments.include? commit.id rescue XMLRPC::FaultException, RuntimeError @@ -106,7 +123,7 @@ def post_bug_comment(bug, repository, commit_messages, branch_name) end message += commit_messages.join("\n\n") begin - xmlrpc_client.call('Bug.add_comment', {'id' => bug, 'comment' => message}) + xmlrpc_authed_call('Bug.add_comment', {'id' => bug, 'comment' => message}) rescue XMLRPC::FaultException # Bug doesn't exist or user can't add comments, do nothing rescue RuntimeError @@ -117,7 +134,7 @@ def post_bug_comment(bug, repository, commit_messages, branch_name) def close_bugs(bug_ids) if bug_ids.length > 0 begin - xmlrpc_client.call('Bug.update', {'ids' => bug_ids, 'status' => 'RESOLVED', 'resolution' => 'FIXED'}) + xmlrpc_authed_call('Bug.update', {'ids' => bug_ids, 'status' => 'RESOLVED', 'resolution' => 'FIXED'}) rescue XMLRPC::FaultException, RuntimeError # Bug doesn't exist, user can't close bug, or version < 4.0 that doesn't support Bug.update. # Do nothing diff --git a/services/campfire.rb b/lib/services/campfire.rb similarity index 67% rename from services/campfire.rb rename to lib/services/campfire.rb index 50d1a1783..2b129da0d 100644 --- a/services/campfire.rb +++ b/lib/services/campfire.rb @@ -5,13 +5,15 @@ class << self self.campfire_class = Tinder::Campfire - string :subdomain, :room, :token + string :subdomain, :room, :sound + password :token boolean :master_only, :play_sound, :long_url + white_list :subdomain, :room default_events :push, :pull_request, :issues def receive_push - url = data['long_url'].to_i == 1 ? summary_url : shorten_url(summary_url) + url = configured_summary_url messages = [] messages << "#{summary_message}: #{url}" messages += commit_messages.first(8) @@ -25,24 +27,32 @@ def receive_push end def receive_pull_request - send_messages summary_message if opened? + message = "#{summary_message}: #{configured_summary_url}" + send_messages message if action =~ /(open)|(close)/ end alias receive_issues receive_pull_request + def receive_public + send_messages "#{summary_message}: #{configured_summary_url}" + end + + alias receive_gollum receive_public + def send_messages(messages) raise_config_error 'Missing campfire token' if data['token'].to_s.empty? - return if data['master_only'].to_i == 1 && respond_to?(:branch_name) && branch_name != 'master' + return if config_boolean_true?('master_only') && respond_to?(:branch_name) && branch_name != 'master' - play_sound = data['play_sound'].to_i == 1 + play_sound = config_boolean_true?('play_sound') + sound = data['sound'].blank? ? 'rimshot' : data['sound'] unless room = find_room raise_config_error 'No such campfire room' end Array(messages).each { |line| room.speak line } - room.play "rimshot" if play_sound && room.respond_to?(:play) + room.play sound if play_sound && room.respond_to?(:play) rescue OpenSSL::SSL::SSLError => boom raise_config_error "SSL Error: #{boom}" rescue Tinder::AuthenticationFailed => boom @@ -60,6 +70,10 @@ def campfire_domain data['subdomain'].to_s.sub /\.campfirenow\.com$/i, '' end + def configured_summary_url + config_boolean_true?('long_url') ? summary_url : shorten_url(summary_url) + end + def find_room room = campfire.find_room_by_name(data['room']) rescue StandardError diff --git a/services/cia.rb b/lib/services/cia.rb similarity index 74% rename from services/cia.rb rename to lib/services/cia.rb index 5fbcacf1c..e9afbdb1a 100644 --- a/services/cia.rb +++ b/lib/services/cia.rb @@ -1,6 +1,7 @@ class Service::CIA < Service - string :address, :project, :branch - boolean :long_url + string :address, :project, :branch, :module + boolean :long_url, :full_commits + white_list :address, :project, :branch, :module def receive_push repository = @@ -17,15 +18,17 @@ def receive_push ref_name end + module_name = data['module'].to_s + commits = payload['commits'] if commits.size > 5 - message = build_cia_commit(repository, branch, payload['after'], commits.last, commits.size - 1) + message = build_cia_commit(repository, branch, payload['after'], commits.last, module_name, commits.size - 1) deliver(message) else commits.each do |commit| sha1 = commit['id'] - message = build_cia_commit(repository, branch, sha1, commit) + message = build_cia_commit(repository, branch, sha1, commit, module_name) deliver(message) end end @@ -36,10 +39,10 @@ def xmlrpc_server @xmlrpc_server ||= begin XMLRPC::Client.new2( (address = data['address'].to_s).present? ? - address : 'http://cia.vc') + address : 'http://cia.vc/xmlrpc.php') end end - + def deliver(message) xmlrpc_server.call("hub.deliver", message) rescue StandardError => err @@ -50,17 +53,24 @@ def deliver(message) end end - def build_cia_commit(repository, branch, sha1, commit, size = 1) - log = commit['message'] + def build_cia_commit(repository, branch, sha1, commit, module_name, size = 1) + log_lines = commit['message'].split("\n") + log = log_lines.shift log << " (+#{size} more commits...)" if size > 1 dt = DateTime.parse(commit['timestamp']).new_offset timestamp = Time.send(:gm, dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec).to_i files = commit['modified'] + commit['added'] + commit['removed'] - tiny_url = data['long_url'].to_i == 1 ? commit['url'] : shorten_url(commit['url']) + tiny_url = config_boolean_true?('long_url') ? commit['url'] : shorten_url(commit['url']) log << " - #{tiny_url}" + if config_boolean_true?('full_commits') + log_lines.each do |log_line| + log << "\n" << log_line + end + end + <<-MSG @@ -71,6 +81,7 @@ def build_cia_commit(repository, branch, sha1, commit, size = 1) #{repository} #{branch} + #{module_name} #{timestamp} diff --git a/lib/services/clever_cloud.rb b/lib/services/clever_cloud.rb new file mode 100644 index 000000000..0496380a7 --- /dev/null +++ b/lib/services/clever_cloud.rb @@ -0,0 +1,32 @@ +class Service::CleverCloud < Service + include HttpHelper + + self.title = 'Clever Cloud' + + password :secret + + default_events :push + + url "https://www..clever-cloud.com/" + logo_url "http://cleverstatic.cleverapps.io/twitter-cards-assets/tw-card-logo.png" + maintained_by :github => 'Keruspe' + supported_by :web => 'https://www.clever-cloud.com/', + :email => 'support@clever-cloud.com' + + def receive_push + secret_ok = required_config_value ("secret") + + http.headers['X-GitHub-Event'] = event.to_s + http.headers['X-GitHub-Delivery'] = delivery_guid.to_s + + res = deliver 'https://api.clever-cloud.com/v2/github/redeploy/', :content_type => 'application/json', :secret => secret_ok + + if res.status < 200 || res.status > 299 + raise_config_error "Invalid HTTP Response: #{res.status}" + end + end + + def original_body + payload + end +end diff --git a/lib/services/codeclimate.rb b/lib/services/codeclimate.rb new file mode 100644 index 000000000..8ac70bf89 --- /dev/null +++ b/lib/services/codeclimate.rb @@ -0,0 +1,24 @@ +class Service::CodeClimate < Service::HttpPost + password :token + + default_events :push, :pull_request + + url "http://codeclimate.com" + + maintained_by :github => "mrb" + + supported_by :web => "https://codeclimate.com/contact", + :email => "hello@codeclimate.com", + :twitter => "@codeclimate" + + def receive_event + token = required_config_value('token') + http.basic_auth "github", token + deliver "https://codeclimate.com/github_events", insecure_ssl: true + end + + def token + data["token"].to_s.strip + end + +end diff --git a/lib/services/codereviewhub.rb b/lib/services/codereviewhub.rb new file mode 100644 index 000000000..c18b6a3d9 --- /dev/null +++ b/lib/services/codereviewhub.rb @@ -0,0 +1,11 @@ +require File.expand_path('../web', __FILE__) + +class Service::Codereviewhub < Service::Web + self.title = "CodeReviewHub" + url "https://www.codereviewhub.com" + logo_url "https://www.codereviewhub.com/favicon.ico" + + supported_by :email => 'contact@codereviewhub.com' + maintained_by :github => 'codereviewhub' + default_events :pull_request, :issue_comment, :commit_comment, :pull_request_review_comment +end diff --git a/lib/services/codeship.rb b/lib/services/codeship.rb new file mode 100644 index 000000000..8b178a244 --- /dev/null +++ b/lib/services/codeship.rb @@ -0,0 +1,29 @@ +class Service::Codeship < Service::HttpPost + string :project_uuid + + url "http://www.codeship.io" + logo_url "http://www.codeship.io/logo_codeship_topbar.png" + + default_events :push, :pull_request + + maintained_by github: 'beanieboi', + twitter: '@beanieboi' + supported_by web: 'http://www.codeship.io/contact', + email: 'support@codeship.io', + twitter: '@codeship' + + def receive_event + http.headers['X-GitHub-Event'] = event.to_s + http_post codeship_url, payload: generate_json(payload) + end + + private + + def project_uuid + required_config_value('project_uuid') + end + + def codeship_url + "https://lighthouse.codeship.io/github/#{project_uuid}" + end +end diff --git a/lib/services/commandoio.rb b/lib/services/commandoio.rb new file mode 100644 index 000000000..9b28a6a50 --- /dev/null +++ b/lib/services/commandoio.rb @@ -0,0 +1,104 @@ +# Commando.io GitHub services integration +class Service::Commandoio < Service::HttpPost + + # Hook information + self.title = 'Commando.io' + self.hook_name = 'commandoio' + + # Support and maintainer information + url 'https://commando.io' + logo_url 'https://static.commando.io/img/favicon-250.png' + + supported_by :web => 'https://commando.io', + :twitter => '@commando_io', + :email => 'hello@commando.io' + + maintained_by :web => 'https://unscramble.co.jp', + :github => 'aw', + :twitter => '@alexandermensa' + + # Form fields + password :api_token_secret_key + + string :account_alias, + :recipe, + :server, + :groups, + :notes + + boolean :halt_on_stderr + + # Only include these in the debug logs + white_list :account_alias, + :recipe, + :server, + :groups, + :halt_on_stderr + + def receive_event + validate_config + validate_server_groups + + url = "recipes/#{data['recipe']}/execute" + + http.basic_auth data['account_alias'], data['api_token_secret_key'] + + http.ssl[:verify] = true + http.url_prefix = "https://api.commando.io/v1/" + + groups = data['groups'].split(',').map {|x| x.strip } if data['groups'] + + params = { :payload => generate_json(payload) } + + params.merge!(:server => data['server']) if data['server'] + params.merge!(:groups => groups) unless groups.nil? + params.merge!(:halt_on_stderr => data['halt_on_stderr']) if config_boolean_true?('halt_on_stderr') + params.merge!(:notes => CGI.escape(data['notes'])) if data['notes'] + + http_post url, params + end + + # Validates the required config values + # + # Raises an error if a config value is invalid or empty + def validate_config + %w(api_token_secret_key account_alias recipe server groups).each {|key| + raise_config_error("Invalid or empty #{key}") if is_error? key, config_value(key) + } + end + + # Validates the server and groups config values + # + # Raises an error if one or the other is missing, or if both are set (XOR) + def validate_server_groups + # XOR the server and groups + raise_config_error("Server or Groups must be set, but not both.") unless config_value('server').empty? ^ config_value('groups').empty? + end + + # Check if there's an error in the provided value + # + # Returns a boolean + def is_error?(key, value) + case key + when 'api_token_secret_key' then true if value.empty? || + value !~ /\Askey_[a-zA-Z0-9]+\z/ + + when 'account_alias' then true if value.empty? || + value.length > 15 || + value !~ /\A[a-z0-9]+\z/ + + when 'recipe' then true if value.empty? || + value.length > 25 || + value !~ /\A[a-zA-Z0-9_]+\z/ + + when 'server' then true if !value.empty? && + value !~ /\A[a-zA-Z0-9_]+\z/ + + when 'groups' then true if !value.empty? && + value !~ /\A[a-zA-Z0-9_\s\,]+\z/ + else + false + end + end + +end diff --git a/lib/services/conductor.rb b/lib/services/conductor.rb new file mode 100644 index 000000000..af4228b8f --- /dev/null +++ b/lib/services/conductor.rb @@ -0,0 +1,21 @@ +class Service::Conductor < Service + string :api_key + white_list :api_key + + default_events :push + + url "https://conductor-app.com" + logo_url "#{url}/logo-blue.png" + + maintained_by :github => 'Shuntyard' + supported_by :email => 'support@conductor-app.com' + + def receive_push + api_key = data['api_key'] + + http.ssl[:verify] = false + http.headers['X-GitHub-Event'] = event.to_s + http_post "#{self.class.url}/github/commit/#{api_key}", :payload => generate_json(payload) + end + +end diff --git a/lib/services/copperegg.rb b/lib/services/copperegg.rb new file mode 100644 index 000000000..409765da9 --- /dev/null +++ b/lib/services/copperegg.rb @@ -0,0 +1,31 @@ +class Service::CopperEgg < Service + string :url, :tag + boolean :master_only + string :api_key + + def receive_push + + raise_config_error 'API Key must be set' if data['api_key'].blank? + + if config_boolean_true?('master_only') && branch_name != 'master' + return + end + + http.ssl[:verify] = false + http.basic_auth(data['api_key'], "U") + http.headers['Content-Type'] = 'application/json' + if data['url'] != "" && data['url'] != nil + url = data['url'] + else + url = "https://api.copperegg.com/v2/annotations.json" + end + note = "GitHub: #{payload['pusher']['name']} has pushed #{payload['commits'].size} commit(s) to #{payload['repository']['name']}" + body = {"note" => note, "starttime" => Time.now.to_i - 30, "endtime" => Time.now.to_i + 30, "tags" => data['tag']} + json = generate_json(body) + + res = http_post url, json + if res.status < 200 || res.status > 299 + raise_config_error + end + end +end diff --git a/lib/services/crocagile.rb b/lib/services/crocagile.rb new file mode 100644 index 000000000..84c6a1687 --- /dev/null +++ b/lib/services/crocagile.rb @@ -0,0 +1,16 @@ +class Service::Crocagile < Service::HttpPost + string :project_key + url "https://www.crocagile.com/home" + logo_url "https://www.crocagile.com/_images/crocagile100x100t.png" + maintained_by :github => 'noelbaron', + :twitter => 'noelbaron' + supported_by :web => 'https://www.crocagile.com/home', + :email => 'support@crocagile.com', + :twitter => 'crocagilehelp' + + def receive_event + raise_config_error "Please enter your Project Key (located via Project Settings screen)." if data['project_key'].to_s.empty? + http.headers['Content-Type'] = 'application/json' + deliver "https://www.crocagile.com/api/integration/github" + end +end diff --git a/lib/services/deploy_hq.rb b/lib/services/deploy_hq.rb new file mode 100644 index 000000000..d5c70e9bb --- /dev/null +++ b/lib/services/deploy_hq.rb @@ -0,0 +1,25 @@ +class Service::DeployHq < Service + string :deploy_hook_url + boolean :email_pusher + + url "http://www.deployhq.com/" + logo_url "http://www.deployhq.com/images/deploy/logo.png" + maintained_by :github => 'darkphnx' + supported_by :web => 'http://support.deployhq.com/', :email => 'support@deployhq.com' + + def receive_push + unless data['deploy_hook_url'].to_s =~ /^https:\/\/[a-z0-9\-\_]+\.deployhq.com\/deploy\/[a-z0-9\-\_]+\/to\/[a-z0-9\-\_]+\/[a-z0-9]+$/i + raise_config_error "Deploy Hook invalid" + end + email_pusher = config_boolean_true?('email_pusher') + + http.url_prefix = data['deploy_hook_url'] + http.headers['content-type'] = 'application/x-www-form-urlencoded' + http.headers['X-GitHub-Event'] = 'push' + + body = Faraday::Utils.build_nested_query(http.params.merge(:payload => generate_json(payload), :notify => email_pusher)) + + http_post data['deploy_hook_url'], body + end + +end diff --git a/lib/services/deployervc.rb b/lib/services/deployervc.rb new file mode 100644 index 000000000..68f2f85c9 --- /dev/null +++ b/lib/services/deployervc.rb @@ -0,0 +1,44 @@ +require 'uri' + +class Service::Deployervc < Service::HttpPost + string :deployment_address + password :api_token + + white_list :deployment_address + + default_events :push + + url "https://deployer.vc" + + maintained_by :github => 'deployervc-emre' + + supported_by :web => 'https://deployer.vc/support.html', + :email => 'support@deployer.vc' + + + def receive_event + deploymentaddress = required_config_value('deployment_address') + apitoken = required_config_value('api_token') + + begin + URI.parse(deploymentaddress) + rescue URI::InvalidURIError + raise_config_error("Invalid URL for deployment address: #{deploymentaddress}") + end + + parts = URI.split(deploymentaddress) + scheme = parts[0] + host = parts[2] + path = parts.last + deployment_id = path.split('/').last + + http.headers['X-Deployervc-Token'] = apitoken + http.headers['Content-Type'] = 'application/json' + http.headers['Accept'] = 'application/json' + + http.url_prefix = "#{scheme}://#{host}" + + url = "/api/v1/deployments/deploy/#{deployment_id}" + http_post(url, generate_json({:revision => ''})) + end +end \ No newline at end of file diff --git a/lib/services/divecloud.rb b/lib/services/divecloud.rb new file mode 100644 index 000000000..1051aff0b --- /dev/null +++ b/lib/services/divecloud.rb @@ -0,0 +1,41 @@ +class Service::DiveCloud < Service::HttpPost + title 'DiveCloud - Application Performance Testing' + url "https://divecloud.nouvola.com" + logo_url "http://www.nouvola.com/wp-content/uploads/2014/01/nouvola_logo_reg1.png" + + maintained_by github: 'prossaro' + supported_by web: 'http://www.nouvola.com/contact', + email: 'support@nouvola.com', + twitter: '@NouvolaTech' + + default_events :deployment_status + string :plan_id + password :api_key, :creds_pass + boolean :random_timing + white_list :plan_id, :random_timing + + + def receive_deployment_status + #Only run performance test after successful deployment + return unless payload['status'] == "success" + + #Sanitize instances of data[:params] + @plan_id = required_config_value('plan_id') + @api_key = required_config_value('api_key') + @creds_pass = config_value('creds_pass') + @random_timing = config_value('random_timing') + + #Connect to api on port 443 + http.url_prefix = "https://divecloud.nouvola.com" + + #Run test + http.post do |request| + request.url "/api/v1/plans/#{@plan_id}/run" + request.headers['Content-Type'] = 'application/json' + request.headers['x-api'] = @api_key + request.headers['user_agent'] = 'Github Agent' + request.body = generate_json(:creds_pass => @creds_pass, :random_timing => @random_timing ) + end + end + +end \ No newline at end of file diff --git a/lib/services/djangopackages.rb b/lib/services/djangopackages.rb new file mode 100644 index 000000000..5cb4ac4a6 --- /dev/null +++ b/lib/services/djangopackages.rb @@ -0,0 +1,20 @@ +class Service::DjangoPackages < Service::HttpPost + + default_events :push + + url "https://www.djangopackages.com/" + logo_url "https://s3.amazonaws.com/opencomparison/img/logo_squares.png" + + maintained_by :github => 'pydanny', + :twitter => '@pydanny' + + supported_by :email => 'pydanny@gmail.com', + :twitter => '@pydanny' + + # ssl_version 2 + + def receive_push + url = "https://www.djangopackages.com/packages/github-webhook/" + http_post url, :payload => generate_json(payload) + end +end diff --git a/lib/services/docker.rb b/lib/services/docker.rb new file mode 100644 index 000000000..58b1e6772 --- /dev/null +++ b/lib/services/docker.rb @@ -0,0 +1,19 @@ +class Service::Docker < Service::HttpPost + + url "http://www.docker.com" + logo_url "http://www.docker.com/static/img/nav/docker-logo-loggedout.png" + + # kencochrane on GitHub/twitter is pinged for any bugs with the Hook code. + maintained_by :github => 'kencochrane', + :twitter => '@kencochrane' + + # Support channels for user-level Hook problems (service failure, + # misconfigured + supported_by :github => 'kencochrane', + :twitter => '@docker' + + def receive_event + deliver "https://registry.hub.docker.com/hooks/github" + end +end + diff --git a/lib/services/email.rb b/lib/services/email.rb new file mode 100644 index 000000000..2d4e8b587 --- /dev/null +++ b/lib/services/email.rb @@ -0,0 +1,326 @@ +class Net::SMTP + def mailfrom(from_addr) + from_addr = from_addr[/<([^>]+)>/, 1] if from_addr.include?('<') + + if $SAFE > 0 + raise SecurityError, 'tainted from_addr' if from_addr.tainted? + end + getok("MAIL FROM:<#{from_addr}>") + end +end + +class Service::Email < Service + string :address + password :secret + boolean :send_from_author + white_list :address + + def receive_push + extend PushEmail + deliver_to_addresses + end + + def receive_public + extend PublicEmail + deliver_to_addresses + end + + def deliver_to_addresses + if addresses.size.zero? + raise_config_error "Invalid addresses: #{data['address'].inspect}" + end + + configure_mail_defaults unless mail_configured? + + addresses.each do |address| + send_message_to address + end + end + + def send_message_to(address) + send_mail mail_message(address) + end + + def send_mail(mail) + mail.deliver! + end + + def configure_mail_defaults + my = self + + Mail.defaults do + delivery_method :smtp, + :address => my.smtp_address, + :port => my.smtp_port, + :domain => my.smtp_domain, + :user_name => my.smtp_user_name, + :password => my.smtp_password, + :authentication => my.smtp_authentication, + :enable_starttls_auto => my.smtp_enable_starttls_auto?, + :openssl_verify_mode => my.smtp_openssl_verify_mode + end + + mail_configured! + end + + def mail_configured? + defined?(@@mail_configured) && @@mail_configured + end + + def mail_configured! + @@mail_configured = true + end + + def addresses + @addresses ||= data['address'].to_s.split(' ').slice(0, 2) + end + + def mail_message(address) + my = self + + Mail.new do + to address + from my.mail_from + reply_to my.mail_from + subject my.mail_subject + headers my.secret_header + + text_part do + content_type 'text/plain; charset=UTF-8' + body my.mail_body + end + end + end + + def secret_header + secret ? {'Approved' => secret} : {} + end + + def secret + data['secret'] if data['secret'].to_s.size > 0 + end + + def shorten(text, limit) + text.slice(0, limit) << '...' + end + + def align(text, indent = ' ') + margin = text[/\A\s+/].size + + text.gsub(/^\s{#{margin}}/, indent) + end + + def send_from_author? + config_boolean_true?('send_from_author') + end + + def smtp_address + @smtp_address ||= email_config['address'] + end + + def smtp_port + @smtp_port ||= (email_config['port'] || 25).to_i + end + + def smtp_domain + @smtp_domain ||= email_config['domain'] || 'localhost.localdomain' + end + + def smtp_authentication + @smtp_authentication ||= email_config['authentication'] + end + + def smtp_user_name + @smtp_user_name ||= email_config['user_name'] + end + + def smtp_password + @smtp_password ||= email_config['password'] + end + + def smtp_enable_starttls_auto? + @smtp_enable_starttls_auto ||= (email_config['enable_starttls_auto'] != 'false' && true) + end + + def smtp_openssl_verify_mode + @smtp_openssl_verify_mode ||= email_config['openssl_verify_mode'] + end + + def smtp_logging? + @smtp_logging ||= email_config['enable_logging'] + end + + def noreply_address + @noreply_address ||= email_config['noreply_address'] || "GitHub " + end + + module RepositoryEmailHelpers + def name_with_owner + File.join(owner_name, repository_name) + end + + def owner_name + payload['repository']['owner']['login'] + end + + def repository_name + payload['repository']['name'] + end + + def repo_url + payload['repository']['url'] + end + end + + module PushEmail + include RepositoryEmailHelpers + + # Public + def mail_subject + if first_commit + "[#{name_with_owner}] #{first_commit_sha.slice(0, 6)}: #{first_commit_title}" + else + "[#{name_with_owner}]" + end + end + + # Public + def mail_body + body = commits.inject(repository_text) do |text, commit| + text << commit_text(commit) + end + + body << compare_text unless single_commit? + + body << <<-NOTE + + **NOTE:** GitHub Services has been marked for deprecation: https://developer.github.com/changes/2018-04-25-github-services-deprecation/ + + We will provide an alternative path for the email notifications by January 31st, 2019. + NOTE + end + + # Public + def mail_from + send_from_author? ? author_address : noreply_address + end + + def repository_text + align(<<-EOH) + Branch: #{branch_ref} + Home: #{repo_url} + EOH + end + + def commit_text(commit) + gitsha = commit['id'] + added = commit['added'].map { |f| ['A', f] } + removed = commit['removed'].map { |f| ['R', f] } + modified = commit['modified'].map { |f| ['M', f] } + + changed_paths = (added + removed + modified).sort_by { |(char, file)| file } + changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n ") + + timestamp = Date.parse(commit['timestamp']) + + commit_author = "#{commit['author']['name']} <#{commit['author']['email']}>" + + text = align(<<-EOH) + Commit: #{gitsha} + #{commit['url']} + Author: #{commit_author} + Date: #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')}) + + EOH + + if changed_paths.size > 0 + text << align(<<-EOH) + Changed paths: + #{changed_paths} + + EOH + end + + text << align(<<-EOH) + Log Message: + ----------- + #{commit['message']} + + + EOH + + text + end + + def compare_text + "Compare: #{payload['compare']}" + end + + def single_commit? + first_commit == last_commit + end + + def branch_ref + payload['ref'] + end + + def author_address + "#{author_name} <#{author_email}>" + end + + def author + commit = last_commit || {} + commit['author'] || commit['committer'] || payload['pusher'] + end + + def author_name + author['name'] + end + + def author_email + author['email'] + end + + def last_commit + payload['commits'].last # assume that the last committer is also the pusher + end + + def first_commit_sha + first_commit['id'] + end + + def first_commit_title(limit = 50) + title_line = first_commit['message'][/\A[^\n]+/] || '' + + title_line.length > limit ? shorten(title_line, limit) : title_line + end + + def first_commit + payload['commits'].first + end + + # must be a difference with push payloads + def owner_name + payload['repository']['owner']['name'] + end + end + + module PublicEmail + include RepositoryEmailHelpers + + # Public + def mail_subject + "#{name_with_owner} has changed from Private to Public" + end + + # Public + def mail_body + "#{name_with_owner} has changed from Private to Public\n\n#{repo_url}" + end + + # Public + def mail_from + noreply_address + end + end +end diff --git a/lib/services/firebase.rb b/lib/services/firebase.rb new file mode 100644 index 000000000..0f466d12b --- /dev/null +++ b/lib/services/firebase.rb @@ -0,0 +1,45 @@ +class Service::Firebase < Service + string :firebase, :secret + white_list :firebase + + url 'https://www.firebase.com' + logo_url 'https://www.firebase.com/images/logo.png' + maintained_by :github => 'anantn' + supported_by :email => 'support@firebase.com' + + def receive_push + url = data['firebase'].to_s + url.gsub! /\s/, '' + + if url.empty? + raise_config_error 'Invalid URL.' + end + + if url !~ /^https\:\/\// + raise_config_error 'Invalid URL (did you include the https prefix?)' + end + + if url !~ /^.*\.json$/ + url = url + '.json' + end + + secret = data['secret'].to_s + if secret.length > 0 + url = url + '?auth=' + secret + end + + payload['commits'].each do |commit| + http_post url, generate_json(commit) + end + rescue Addressable::URI::InvalidURIError, Errno::EHOSTUNREACH + raise_missing_error $!.to_s + rescue SocketError + if $!.to_s =~ /getaddrinfo:/ + raise_missing_error 'Invalid host name.' + else + raise + end + rescue EOFError + raise_config_error 'Invalid server response.' + end +end diff --git a/lib/services/fisheye.rb b/lib/services/fisheye.rb new file mode 100644 index 000000000..f61bc7a01 --- /dev/null +++ b/lib/services/fisheye.rb @@ -0,0 +1,64 @@ +class Service::FishEye < Service + + string :url_base, :repository_name + password :token + white_list :url_base, :repository_name + + def receive_push + + verify_config + + http.headers['X-Api-Key'] = token + http.headers['Content-Type'] = 'application/json' + + url = "%s/rest-service-fecru/admin/repositories-v1/%s/scan" % [url_base, repository_name] + + res = http_post url + + handle_response(res) + + end + + def handle_response(response) + case response.status + when 200 + "Ok" + when 401 + raise_config_error("Invalid REST API token") + when 404 + raise_config_error("Invalid repository name") + else + msg = "#{url_base}, repository: #{repository_name} with token: #{token.to_s.strip.length != 0}" + raise_config_error("Error occurred: #{response.status} when connecting to: #{msg}") + end + end + + def verify_config + %w(url_base token repository_name).each do |var| + raise_config_error "Missing configuration: #{var}" if send(var).to_s.empty? + end + end + + def repository_name + @repository_name ||= (data['repository_name'].to_s.strip.length != 0) ? data['repository_name'] : payload['repository']['name'] + end + + def url_base + @url_base ||= begin + url_base = data['url_base'] + if (!(url_base.nil? || url_base.empty?)) + if url_base !~ /^https?\:\/\// + url_base = "http://#{url_base}" + end + url_base = url_base.gsub(/\/+$/, '') + end + url_base + end + end + + def token + @token ||= data['token'] + end + +end + diff --git a/lib/services/flowdock.rb b/lib/services/flowdock.rb new file mode 100644 index 000000000..1221c201f --- /dev/null +++ b/lib/services/flowdock.rb @@ -0,0 +1,22 @@ +require 'uri' + +class Service::Flowdock < Service::HttpPost + default_events :commit_comment, :gollum, :issues, :issue_comment, :pull_request, :push, :pull_request_review_comment + password :token + + url "https://www.flowdock.com" + logo_url "https://d2ph5hv9wbwvla.cloudfront.net/github/icon_220x140.png" + maintained_by email: "team@flowdock.com", github: 'Mumakil' + supported_by email: "support@flowdock.com", twitter: "@flowdock" + + def receive_event + raw_token = required_config_value('token') + token = URI.escape(raw_token.to_s.gsub(/\s/, '')) + http.headers['X-GitHub-Event'] = event.to_s + deliver "https://api.flowdock.com/v1/github/#{token}" + end + + def original_body + payload + end +end diff --git a/services/fog_bugz.rb b/lib/services/fog_bugz.rb similarity index 96% rename from services/fog_bugz.rb rename to lib/services/fog_bugz.rb index 83face7f7..eaaf67f85 100644 --- a/services/fog_bugz.rb +++ b/lib/services/fog_bugz.rb @@ -1,5 +1,6 @@ class Service::FogBugz < Service string :cvssubmit_url, :fb_repoid, :fb_version + white_list :cvssubmit_url, :fb_repoid, :fb_version def receive_push if (fb_url = data['cvssubmit_url']).blank? diff --git a/lib/services/freckle.rb b/lib/services/freckle.rb new file mode 100644 index 000000000..b39073bd3 --- /dev/null +++ b/lib/services/freckle.rb @@ -0,0 +1,26 @@ +class Service::Freckle < Service::HttpPost + string :subdomain, :project + password :token + white_list :subdomain, :project + + default_events :push + + url "https://letsfreckle.com" + + maintained_by :github => 'LockeCole117', :twitter => '@LockeCole117' + + supported_by :web => 'https://letsfreckle.com', + :email => 'support@letsfreckle.com', + :twitter => '@letsfreckle' + + def receive_event + subdomain = required_config_value('subdomain').strip + token = required_config_value('token').strip + project = required_config_value('project').strip + + http.headers['X-FreckleToken'] = token + http.headers['X-FreckleProject'] = project + url = "https://#{data['subdomain']}.letsfreckle.com/api/github/commits" + deliver url + end +end diff --git a/lib/services/gemini.rb b/lib/services/gemini.rb new file mode 100644 index 000000000..00a537fb0 --- /dev/null +++ b/lib/services/gemini.rb @@ -0,0 +1,25 @@ +class Service::Gemini < Service + string :server_url, :api_key + + def receive_push + if data['server_url'].to_s.empty? + raise_config_error "Server URL is missing" + end + + if data['api_key'].to_s.empty? + raise_config_error "API Key is missing" + end + + # Sets this basic auth info for every request. GitHub user and Gemini API Key. + http.basic_auth(data['api_key'], data['api_key']) + + # Every request sends JSON. + http.headers['Content-Type'] = 'application/json' + + # Uses this URL as a prefix for every request. + http.url_prefix = '%s/api/github' % [data['server_url']] + + # POST http://localhost/gemini/api/github/commit + http_post "commit", generate_json(payload) + end +end diff --git a/services/gemnasium.rb b/lib/services/gemnasium.rb similarity index 88% rename from services/gemnasium.rb rename to lib/services/gemnasium.rb index bd884d5c6..b8bcdeccd 100644 --- a/services/gemnasium.rb +++ b/lib/services/gemnasium.rb @@ -1,5 +1,7 @@ class Service::Gemnasium < Service - string :user, :token + string :user + password :token + white_list :user def receive_push http.basic_auth(user, signature) @@ -27,7 +29,7 @@ def token end def body - payload.to_json + generate_json(payload) end def url diff --git a/services/get_localization.rb b/lib/services/get_localization.rb similarity index 74% rename from services/get_localization.rb rename to lib/services/get_localization.rb index f1be725fc..7abb13925 100644 --- a/services/get_localization.rb +++ b/lib/services/get_localization.rb @@ -1,12 +1,14 @@ class Service::GetLocalization < Service - string :project_name, :project_token + string :project_name + password :project_token + white_list :project_name def receive_push project_name = data['project_name'] project_token = data['project_token'] res = http_post "https://www.getlocalization.com/services/github/notify/#{project_name}/#{project_token}/", - :payload => payload.to_json + :payload => generate_json(payload) if res.status < 200 || res.status > 299 raise_config_error diff --git a/lib/services/gitter.rb b/lib/services/gitter.rb new file mode 100644 index 000000000..5d3561f8a --- /dev/null +++ b/lib/services/gitter.rb @@ -0,0 +1,30 @@ +class Service::Gitter < Service::HttpPost + password :token + boolean :mute_fork, :mute_watch, :mute_comments, :mute_wiki + + default_events ALL_EVENTS + + url 'https://gitter.im' + logo_url 'https://gitter.im/_s/1/images/2/gitter/logo-blue-text.png' + + maintained_by github: 'malditogeek', + twitter: '@malditogeek' + + supported_by github: 'gitterHQ', + twitter: '@gitchat', + email: 'support@gitter.im' + + def receive_event + token = required_config_value('token') + raise_config_error 'Invalid token' unless token.match(/^\w+$/) + + return if config_boolean_true?('mute_fork') && event.to_s =~ /fork/ + return if config_boolean_true?('mute_watch') && event.to_s =~ /watch/ + return if config_boolean_true?('mute_comments') && event.to_s =~ /comment/ + return if config_boolean_true?('mute_wiki') && event.to_s =~ /gollum/ + + http.headers['X-GitHub-Event'] = event.to_s + + deliver "https://webhooks.gitter.im/e/#{token}" + end +end diff --git a/lib/services/gocd.rb b/lib/services/gocd.rb new file mode 100644 index 000000000..142303f9b --- /dev/null +++ b/lib/services/gocd.rb @@ -0,0 +1,59 @@ +class Service::GoCD < Service + string :base_url, :repository_url, :username + password :password + boolean :verify_ssl + white_list :base_url, :repository_url, :username + + url "http://www.go.cd/" + logo_url "http://www.go.cd/images/logo-go-home_2014.png" + maintained_by github: "manojlds" + + def receive_push + return if payload['deleted'] + validate_config + + http.ssl[:verify] = verify_ssl + http.url_prefix = base_url + http.headers['confirm'] = true + + http.basic_auth username, password if username.present? and password.present? + + res = http_post "go/api/material/notify/git", repository_url: repository_url + case res.status + when 200..299 + when 403, 401, 422 then raise_config_error("Invalid credentials") + when 404, 301, 302 then raise_config_error("Invalid Go server URL") + else raise_config_error("HTTP: #{res.status}") + end + rescue SocketError => e + raise_config_error "Invalid Go sever host" if e.to_s =~ /getaddrinfo: Name or service not known/ + raise + end + + def validate_config + %w(base_url repository_url).each do |var| + raise_config_error "Missing configuration: #{var}" if send(var).to_s.empty? + end + end + + def base_url + @base_url ||= data['base_url'] + end + + def repository_url + @build_key ||= data['repository_url'] + end + + def username + @username ||= data['username'] + end + + def password + @password ||= data['password'] + end + + def verify_ssl + # If verify SSL has never been set, let's default to true + @verify_ssl ||= data['verify_ssl'].nil? || config_boolean_true?('verify_ssl') + end +end diff --git a/lib/services/grove.rb b/lib/services/grove.rb new file mode 100644 index 000000000..9735fb5e5 --- /dev/null +++ b/lib/services/grove.rb @@ -0,0 +1,12 @@ +class Service::Grove < Service + default_events :commit_comment, :gollum, :issues, :issue_comment, :pull_request, :push + password :channel_token + + def receive_push + token = data['channel_token'].to_s + raise_config_error "Missing channel token" if token.empty? + + res = http_post "https://grove.io/api/services/github/#{token}", + :payload => generate_json(payload) + end +end diff --git a/lib/services/habitualist.rb b/lib/services/habitualist.rb new file mode 100644 index 000000000..d1795f92e --- /dev/null +++ b/lib/services/habitualist.rb @@ -0,0 +1,6 @@ +class Service::Habitualist < Service + def receive_push + http_post "https://habitualist.com/webhooks/github/", + :payload => generate_json(payload) + end +end diff --git a/services/harvest.rb b/lib/services/harvest.rb similarity index 90% rename from services/harvest.rb rename to lib/services/harvest.rb index e8fa086a7..957bdd212 100644 --- a/services/harvest.rb +++ b/lib/services/harvest.rb @@ -9,6 +9,7 @@ class Service::Harvest < Service string :subdomain, :username password :password boolean :ssl + white_list :subdomain, :username def receive_push if data['username'].to_s.empty? @@ -21,7 +22,8 @@ def receive_push http.basic_auth(data['username'], data['password']) http.headers['Content-Type'] = 'application/xml' http.headers['Accept'] = 'application/xml' - http.url_prefix = "http#{:s if data['ssl']}://#{data['subdomain']}.harvestapp.com/" + protocol = config_boolean_true?('ssl') ? 'https' : 'http' + http.url_prefix = "#{protocol}://#{data['subdomain']}.harvestapp.com/" statuses = [] repository = payload['repository']['name'] diff --git a/lib/services/heroku_beta.rb b/lib/services/heroku_beta.rb new file mode 100644 index 000000000..b10fc08dc --- /dev/null +++ b/lib/services/heroku_beta.rb @@ -0,0 +1,175 @@ +require 'base64' + +class Service::HerokuBeta < Service::HttpPost + string :name, :github_api_url + password :heroku_token, :github_token + + white_list :name, :github_api_url + + default_events :deployment + + url 'https://heroku.com' + logo_url 'https://camo.githubusercontent.com/edbc46e94fd4e9724da99bdd8da5d18e82f7b737/687474703a2f2f7777772e746f756368696e737069726174696f6e2e636f6d2f6173736574732f6865726f6b752d6c6f676f2d61663863386230333462346261343433613632376232393035666337316138362e706e67' + + maintained_by :github => 'atmos', :twitter => '@atmos' + + supported_by :web => 'https://github.com/contact', + :email => 'support@github.com', + :twitter => '@atmos' + + def github_repo_path + payload['repository']['full_name'] + end + + def environment + payload['deployment']['environment'] + end + + def ref + payload['deployment']['ref'] + end + + def sha + payload['deployment']['sha'][0..7] + end + + def version_string + ref == sha ? sha : "#{ref}@#{sha}" + end + + def receive_event + http.ssl[:verify] = true + + case event + when :deployment + heroku_app_access? + github_user_access? + github_repo_access? + deploy + when :status + # create a deployment if the default branch is pushed to and commit + # status is green + raise_config_error_with_message(:no_event_handler) + when :push + # create a deployment if the default branch is pushed to(basic-auto-deploy) + raise_config_error_with_message(:no_event_handler) + else + raise_config_error_with_message(:no_event_handler) + end + end + + def heroku_application_name + required_config_value('name') + end + + def heroku_headers + { + 'Accept' => 'application/vnd.heroku+json; version=3', + 'Content-Type' => "application/json", + "Authorization" => Base64.encode64(":#{required_config_value('heroku_token')}").strip + } + end + + def deploy + response = http_post "https://api.heroku.com/apps/#{heroku_application_name}/builds" do |req| + req.headers.merge!(heroku_headers) + req.body = JSON.dump({:source_blob => {:url => repo_archive_link, :version => version_string}}) + end + raise_config_error_with_message(:no_heroku_app_build_access) unless response.success? + + build_id = JSON.parse(response.body)['id'] + deployment_status_options = { + "state" => "success", + "target_url" => heroku_build_output_url(build_id), + "description" => "Created by GitHub Services@#{Service.current_sha[0..7]}" + } + + deployment_path = "/repos/#{github_repo_path}/deployments/#{payload['deployment']['id']}/statuses" + response = http_post "#{github_api_url}#{deployment_path}" do |req| + req.headers.merge!(default_github_headers) + req.body = JSON.dump(deployment_status_options) + end + raise_config_error_with_message(:no_github_deployment_access) unless response.success? + end + + def heroku_build_output_url(id) + "https://dashboard.heroku.com/apps/#{heroku_application_name}/activity/builds/#{id}" + end + + def heroku_app_access? + response = http_get "https://api.heroku.com/apps/#{heroku_application_name}" do |req| + req.headers.merge!(heroku_headers) + end + unless response.success? + raise_config_error_with_message(:no_heroku_app_access) + end + end + + def github_user_access? + response = github_get("/user") + unless response.success? + raise_config_error_with_message(:no_github_user_access) + end + end + + def github_repo_access? + response = github_get("/repos/#{github_repo_path}") + unless response.success? + raise_config_error_with_message(:no_github_repo_access) + end + end + + def repo_archive_link + response = github_get("/repos/#{github_repo_path}/tarball/#{sha}") + unless response.status == 302 + raise_config_error_with_message(:no_github_archive_link) + end + response.headers['Location'] + end + + def github_get(path) + http_get "#{github_api_url}#{path}" do |req| + req.headers.merge!(default_github_headers) + end + end + + def default_github_headers + { + 'Accept' => "application/vnd.github.cannonball-preview+json", + 'User-Agent' => "Operation: California", + 'Content-Type' => "application/json", + 'Authorization' => "token #{required_config_value('github_token')}" + } + end + + def github_api_url + if config_value("github_api_url").empty? + "https://api.github.com" + else + config_value("github_api_url") + end + end + + def raise_config_error_with_message(sym) + raise_config_error(error_messages[sym]) + end + + def error_messages + @default_error_messages ||= { + :no_event_handler => + "The #{event} event is currently unsupported.", + :no_github_archive_link => + "Unable to generate an archive link for #{github_repo_path} on GitHub with the provided token.", + :no_github_repo_access => + "Unable to access the #{github_repo_path} repository on GitHub with the provided token.", + :no_github_user_access => + "Unable to access GitHub with the provided token.", + :no_github_deployment_access => + "Unable to update the deployment status on GitHub with the provided token.", + :no_heroku_app_access => + "Unable to access #{heroku_application_name} on heroku with the provided token.", + :no_heroku_app_build_access => + "Unable to create a build for #{heroku_application_name} on heroku with the provided token." + } + end +end diff --git a/lib/services/hipchat.rb b/lib/services/hipchat.rb new file mode 100644 index 000000000..ad7fc6ecb --- /dev/null +++ b/lib/services/hipchat.rb @@ -0,0 +1,62 @@ +class Service::HipChat < Service + password :auth_token + string :room, :restrict_to_branch, :color, :server + boolean :notify, :quiet_fork, :quiet_watch, :quiet_comments, :quiet_labels, :quiet_assigning, :quiet_wiki + white_list :room, :restrict_to_branch, :color + + default_events :commit_comment, :download, :fork, :fork_apply, :gollum, + :issues, :issue_comment, :member, :public, :pull_request, :pull_request_review_comment, + :push, :watch + + def receive_event + # make sure we have what we need + raise_config_error "Missing 'auth_token'" if data['auth_token'].to_s == '' + raise_config_error "Missing 'room'" if data['room'].to_s == '' + + server = data['server'].presence || 'api.hipchat.com' + + # push events can be restricted to certain branches + if event.to_s == 'push' + branch = payload['ref'].split('/').last + branch_restriction = data['restrict_to_branch'].to_s + + # check the branch restriction is poplulated and branch is not included + if branch_restriction.length > 0 && branch_restriction.index(branch) == nil + return + end + end + + # ignore forks and watches if boolean is set + return if event.to_s =~ /fork/ && config_boolean_true?('quiet_fork') + return if event.to_s =~ /watch/ && config_boolean_true?('quiet_watch') + return if event.to_s =~ /comment/ && config_boolean_true?('quiet_comments') + return if event.to_s =~ /gollum/ && config_boolean_true?('quiet_wiki') + return if event.to_s =~ /issue|pull_request/ && payload['action'].to_s =~ /label/ && config_boolean_true?('quiet_labels') + return if event.to_s =~ /issue|pull_request/ && payload['action'].to_s =~ /assign/ && config_boolean_true?('quiet_assigning') + + http.headers['X-GitHub-Event'] = event.to_s + + rooms = data['room'].to_s.split(",") + room_ids = if rooms.all? { |room_id| Integer(room_id) rescue false } + rooms + else + [data['room'].to_s] + end + + room_ids.each do |room_id| + params = { + :auth_token => data['auth_token'], + :room_id => room_id, + :payload => generate_json(payload), + :notify => config_boolean_true?('notify') ? 1 : 0 + } + if data['color'].present? + params.merge!(:color => data['color']) + end + res = http_post "https://#{server}/v1/webhooks/github", params + if res.status < 200 || res.status > 299 + raise_config_error + end + end + end +end diff --git a/lib/services/hostedgraphite.rb b/lib/services/hostedgraphite.rb new file mode 100644 index 000000000..de5ff570e --- /dev/null +++ b/lib/services/hostedgraphite.rb @@ -0,0 +1,15 @@ +class Service::Hostedgraphite < Service + string :api_key + + def receive_push + res = http_post "https://www.hostedgraphite.com/integrations/github/", + 'payload' => generate_json(payload), + 'api_key' => data['api_key'] + + if res.status != 200 + raise_config_error + end + + end +end + diff --git a/lib/services/http_post.rb b/lib/services/http_post.rb new file mode 100644 index 000000000..1b83ddb00 --- /dev/null +++ b/lib/services/http_post.rb @@ -0,0 +1,15 @@ +class Service::HttpPost < Service + include HttpHelper + + alias receive_event deliver + + def receive_event + deliver data['url'], :content_type => data['content_type'], + :insecure_ssl => data['insecure_ssl'].to_i == 1, :secret => data['secret'] + end + + def original_body + {:payload => payload, :event => event.to_s, :config => data, + :guid => delivery_guid} + end +end diff --git a/lib/services/huboard.rb b/lib/services/huboard.rb new file mode 100644 index 000000000..c666f471b --- /dev/null +++ b/lib/services/huboard.rb @@ -0,0 +1,21 @@ +class Service::HuBoard < Service::HttpPost + + default_events :issue_comment, :issues + + url "https://huboard.com" + logo_url "https://huboard.com/img/LogoFullPurpleLight.png" + maintained_by :github => 'rauhryan' + supported_by :email => 'support@huboard.com' + + HUBOARD_URL = "https://huboard.com/api/site" + + def receive_issues + http_post "#{HUBOARD_URL}/webhook/issue", :payload => generate_json(payload) + end + + def receive_issue_comment + http_post "#{HUBOARD_URL}/webhook/comment", :payload => generate_json(payload) + end + +end + diff --git a/lib/services/humbug.rb b/lib/services/humbug.rb new file mode 100644 index 000000000..206ff07d9 --- /dev/null +++ b/lib/services/humbug.rb @@ -0,0 +1,78 @@ +class Service::Humbug < Service + self.title = "Zulip" + url = 'https://zulip.com' + logo_url = 'https://zulip.com/static/images/logo/zulip-icon-512x512.png' + maintained_by :github => 'zulip' + supported_by :email => 'support@zulip.com' + + # textboxes + string :email + string :api_key + string :stream + string :commit_stream + string :issue_stream + string :branches + string :alternative_endpoint + + # checkboxes + boolean :exclude_pull_requests + boolean :exclude_issues + boolean :exclude_commits + + # list of things approved for github's logging. See service.rb for more. + white_list :email + white_list :branches + white_list :alternative_endpoint + white_list :exclude_pull_requests + white_list :exclude_issues + white_list :exclude_commits + + # events handled by this GitHub hook + default_events :commit_comment, :create, :delete, :download, :follow, :fork, + :fork_apply, :gist, :gollum, :issue_comment, :issues, :member, :public, + :pull_request, :push, :team_add, :watch, :pull_request_review_comment, + :status + + def receive_event + raise_config_error "Missing 'email'" if data['email'].to_s.empty? + raise_config_error "Missing 'api_key'" if data['api_key'].to_s.empty? + + data['alternative_endpoint'] = data['alternative_endpoint'].to_s.strip + + if data['alternative_endpoint'].empty? + url = 'https://api.zulip.com/v1/external/github' + else + url = data['alternative_endpoint'] + end + + begin + http.headers['User-Agent'] = 'ZulipGitHubWebhook' + http.headers['Accept'] = 'application/json' + res = http_post url, + :email => data['email'], + :api_key => data['api_key'], + :stream => data['stream'], + :commit_stream => data['commit_stream'], + :issue_stream => data['issue_stream'], + :branches => data['branches'], + :exclude_pull_requests => data['exclude_pull_requests'], + :exclude_issues => data['exclude_issues'], + :exclude_commits => data['exclude_commits'], + + :event => event, + :payload => generate_json(payload), + # The GitHub payload version. Unspecified means an implicit version 1 + :version => '2', + :client => 'ZulipGitHubWebhook' + + + + if not res.success? + raise_config_error ("Server returned status " + res.status.to_s + + ": " + res.body) + end + rescue Errno::ENOENT => e + raise_missing_error (url + " could not be reached") + end + end +end diff --git a/lib/services/ibm_devops.rb b/lib/services/ibm_devops.rb new file mode 100644 index 000000000..2d1a52d8e --- /dev/null +++ b/lib/services/ibm_devops.rb @@ -0,0 +1,41 @@ +class Service::IBMDevOpsServices < Service::HttpPost + string :ibm_id + password :ibm_password + string :override_server_url + title 'IBM Bluemix DevOps Services' + white_list :ibm_id + + default_events :push + + # annotate this service + url "http://hub.jazz.net" + logo_url "https://hub.jazz.net/manage/web/com.ibm.team.jazzhub.web/graphics/HomePage/Powered-by-JH_white.png" + supported_by :web => "https://hub.jazz.net/support", :email => "hub@jazz.net", :twitter => "@JazzHub" + + def receive_push + username = required_config_value('ibm_id') + password = required_config_value('ibm_password') + override_server_url = data['override_server_url'] + server_url = (override_server_url.nil? || override_server_url.empty? ? "https://hub.jazz.net/manage" : override_server_url) + post_url = "#{server_url}/processGitHubPayload?auth_type=ibmlogin" + @request = {:ibm_id => username, :server_url => server_url, :url => post_url} + @response = deliver post_url + verify_post_response + end + + def verify_post_response + case @response.status + when 200, 201, 304 + # OK + when 401 then + raise_config_error("Authentication failed for #{@request[:ibm_id]}: Status=#{@response.status}, Message=#{@response.body}") + when 403 then + raise_config_error("Authorization failure: Status=#{@response.status}, Message=#{@response.body}") + when 404 then + raise_config_error("Invalid git repo URL provided: Status=#{@response.status}, Message=#{@response.body}") + else + raise_config_error("HTTP Error: Status=#{@response.status}, Message=#{@response.body}") + end + end + +end diff --git a/lib/services/icescrum.rb b/lib/services/icescrum.rb new file mode 100644 index 000000000..1cf81a876 --- /dev/null +++ b/lib/services/icescrum.rb @@ -0,0 +1,26 @@ +class Service::IceScrum < Service + string :base_url, :project_key, :username + password :password + def receive_push + raise_config_error "Invalid username" if data['username'].to_s.empty? + raise_config_error "Invalid password" if data['password'].to_s.empty? + raise_config_error "Invalid project key" if data['project_key'].to_s.empty? + + username = data['username'].to_s.gsub(/\s+/, "") + project_key = data['project_key'].to_s.upcase.gsub(/\s+/, "") + password = data['password'].to_s.gsub(/\s+/, "") + + if data['base_url'].present? + url = "#{data['base_url']}/ws/p/#{project_key}/commit" + else + url = "https://www.kagilum.com/a/ws/p/#{project_key}/commit" + end + + http.ssl[:verify] = false + http.basic_auth username, password + + http_post url, { :payload => generate_json(payload) } + end + +end + diff --git a/lib/services/irc.rb b/lib/services/irc.rb new file mode 100644 index 000000000..45ed9050a --- /dev/null +++ b/lib/services/irc.rb @@ -0,0 +1,348 @@ +class Service::IRC < Service + string :server, :port, :room, :nick, :branches + password :password, :nickserv_password + boolean :ssl, :message_without_join, :no_colors, :long_url, :notice + white_list :server, :port, :room, :nick + + default_events :push, :pull_request + + def receive_push + return unless branch_name_matches? + + messages = [] + messages << "#{irc_push_summary_message}: #{fmt_url url}" + messages += distinct_commits.first(3).map { + |commit| self.irc_format_commit_message(commit) + } + send_messages messages + end + + def receive_commit_comment + send_messages "#{irc_commit_comment_summary_message} #{fmt_url url}" + end + + def receive_pull_request + send_messages "#{irc_pull_request_summary_message} #{fmt_url url}" if action =~ /(open)|(close)/ + end + + def receive_pull_request_review_comment + send_messages "#{irc_pull_request_review_comment_summary_message} #{fmt_url url}" + end + + def receive_issues + send_messages "#{irc_issue_summary_message} #{fmt_url url}" if action =~ /(open)|(close)/ + end + + def receive_issue_comment + send_messages "#{irc_issue_comment_summary_message} #{fmt_url url}" + end + + def receive_gollum + send_messages "#{irc_gollum_summary_message} #{fmt_url summary_url}" + end + + def send_messages(messages) + messages = Array(messages) + + if config_boolean_true?('no_colors') + messages.each{|message| + message.gsub!(/\002|\017|\026|\037|\003\d{0,2}(?:,\d{1,2})?/, '')} + end + + rooms = data['room'].to_s + if rooms.empty? + raise_config_error "No rooms: #{rooms.inspect}" + return + end + + rooms = rooms.gsub(",", " ").split(" ").map{|room| room[0].chr == '#' ? room : "##{room}"} + botname = data['nick'].to_s.empty? ? "GitHub#{rand(200)}" : data['nick'][0..16] + command = config_boolean_true?('notice') ? 'NOTICE' : 'PRIVMSG' + + irc_password("PASS", data['password']) if !data['password'].to_s.empty? + irc_puts "NICK #{botname}" + irc_puts "USER #{botname} 8 * :#{irc_realname}" + + loop do + case irc_gets + when / 00[1-4] #{Regexp.escape(botname)} / + break + when /^PING\s*:\s*(.*)$/ + irc_puts "PONG #{$1}" + end + end + + nickserv_password = data['nickserv_password'].to_s + if !nickserv_password.empty? + irc_password("PRIVMSG NICKSERV :IDENTIFY", nickserv_password) + loop do + case irc_gets + when /^:NickServ/i + # NickServ responded somehow. + break + when /^PING\s*:\s*(.*)$/ + irc_puts "PONG #{$1}" + end + end + end + + without_join = config_boolean_true?('message_without_join') + rooms.each do |room| + room, pass = room.split("::") + irc_puts "JOIN #{room} #{pass}" unless without_join + + messages.each do |message| + irc_puts "#{command} #{room} :#{message}" + end + + irc_puts "PART #{room}" unless without_join + end + + irc_puts "QUIT" + irc_gets until irc_eof? + rescue SocketError => boom + if boom.to_s =~ /getaddrinfo: Name or service not known/ + raise_config_error 'Invalid host' + elsif boom.to_s =~ /getaddrinfo: Servname not supported for ai_socktype/ + raise_config_error 'Invalid port' + else + raise + end + rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH + raise_config_error 'Invalid host' + rescue OpenSSL::SSL::SSLError + raise_config_error 'Host does not support SSL' + ensure + emit_debug_log + end + + def irc_gets + response = readable_irc.gets + debug_incoming(clean_string_for_json(response)) unless !response || response.empty? + response + end + + def irc_eof? + readable_irc.eof? + end + + def irc_password(command, password) + real_command = "#{command} #{password}" + debug_command = "#{command} #{'*' * password.size}" + irc_puts(real_command, debug_command) + end + + def irc_puts(command, debug_command=command) + debug_outgoing(debug_command) + writable_irc.puts command + end + + def irc_realname + repo_name = payload["repository"]["name"] + repo_private = payload["repository"]["private"] + + "GitHub IRCBot - #{repo_owner}" + (repo_private == false ? "/#{repo_name}" : "") + end + + def repo_owner + # for (what I presume to be) legacy reasonings, some events send owner login, + # others send owner name. this method accounts for both cases. + # sample: push event returns owner name, pull request event returns owner login + payload["repository"]["owner"]["name"] ? + payload["repository"]["owner"]["name"] : + payload["repository"]["owner"]["login"] + end + + def debug_outgoing(command) + irc_debug_log << ">> #{command.strip}" + end + + def debug_incoming(command) + irc_debug_log << "=> #{command.strip}" + end + + def irc_debug_log + @irc_debug_log ||= [] + end + + def emit_debug_log + return unless irc_debug_log.any? + receive_remote_call("IRC Log:\n#{irc_debug_log.join("\n")}") + end + + def irc + @irc ||= begin + socket = TCPSocket.open(data['server'], port) + socket = new_ssl_wrapper(socket) if use_ssl? + socket + end + end + + alias readable_irc irc + alias writable_irc irc + + def new_ssl_wrapper(socket) + ssl_context = OpenSSL::SSL::SSLContext.new + ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE + ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context) + ssl_socket.sync_close = true + ssl_socket.connect + ssl_socket + end + + def use_ssl? + config_boolean_true?('ssl') + end + + def default_port + use_ssl? ? 6697 : 6667 + end + + def port + data['port'].to_i > 0 ? data['port'].to_i : default_port + end + + def url + config_boolean_true?('long_url') ? summary_url : shorten_url(summary_url) + end + + ### IRC message formatting. For reference: + ### \002 bold \003 color \017 reset \026 italic/reverse \037 underline + ### 0 white 1 black 2 dark blue 3 dark green + ### 4 dark red 5 brownish 6 dark purple 7 orange + ### 8 yellow 9 light green 10 dark teal 11 light teal + ### 12 light blue 13 light purple 14 dark gray 15 light gray + + def fmt_url(s) + "\00302\037#{s}\017" + end + + def fmt_repo(s) + "\00313#{s}\017" + end + + def fmt_name(s) + "\00315#{s}\017" + end + + def fmt_branch(s) + "\00306#{s}\017" + end + + def fmt_tag(s) + "\00306#{s}\017" + end + + def fmt_hash(s) + "\00314#{s}\017" + end + + def irc_push_summary_message + message = [] + message << "[#{fmt_repo repo_name}] #{fmt_name pusher_name}" + + if created? + if tag? + message << "tagged #{fmt_tag tag_name} at" + message << (base_ref ? fmt_branch(base_ref_name) : fmt_hash(after_sha)) + else + message << "created #{fmt_branch branch_name}" + + if base_ref + message << "from #{fmt_branch base_ref_name}" + elsif distinct_commits.empty? + message << "at #{fmt_hash after_sha}" + end + + num = distinct_commits.size + message << "(+\002#{num}\017 new commit#{num != 1 ? 's' : ''})" + end + + elsif deleted? + message << "\00304deleted\017 #{fmt_branch branch_name} at #{fmt_hash before_sha}" + + elsif forced? + message << "\00304force-pushed\017 #{fmt_branch branch_name} from #{fmt_hash before_sha} to #{fmt_hash after_sha}" + + elsif commits.any? and distinct_commits.empty? + if base_ref + message << "merged #{fmt_branch base_ref_name} into #{fmt_branch branch_name}" + else + message << "fast-forwarded #{fmt_branch branch_name} from #{fmt_hash before_sha} to #{fmt_hash after_sha}" + end + + else + num = distinct_commits.size + message << "pushed \002#{num}\017 new commit#{num != 1 ? 's' : ''} to #{fmt_branch branch_name}" + end + + message.join(' ') + end + + def irc_format_commit_message(commit) + short = commit['message'].split("\n", 2).first.to_s + short += '...' if short != commit['message'] + + author = commit['author']['name'] + sha1 = commit['id'] + files = Array(commit['modified']) + dirs = files.map { |file| File.dirname(file) }.uniq + + "#{fmt_repo repo_name}/#{fmt_branch branch_name} #{fmt_hash sha1[0..6]} " + + "#{fmt_name commit['author']['name']}: #{short}" + end + + def irc_issue_summary_message + "[#{fmt_repo repo.name}] #{fmt_name sender.login} #{action} issue \##{issue.number}: #{issue.title}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def irc_issue_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + "[#{fmt_repo repo.name}] #{fmt_name sender.login} commented on issue \##{issue.number}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def irc_commit_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + sha1 = comment.commit_id + "[#{fmt_repo repo.name}] #{fmt_name sender.login} commented on commit #{fmt_hash sha1[0..6]}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def irc_pull_request_summary_message + base_ref = pull.base.label.split(':').last + head_ref = pull.head.label.split(':').last + head_label = head_ref != base_ref ? head_ref : pull.head.label + + "[#{fmt_repo repo.name}] #{fmt_name sender.login} #{action} pull request " + + "\##{pull.number}: #{pull.title} (#{fmt_branch base_ref}...#{fmt_branch head_ref})" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def irc_pull_request_review_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + sha1 = comment.commit_id + "[#{fmt_repo repo.name}] #{fmt_name sender.login} commented on pull request " + + "\##{pull_request_number} #{fmt_hash sha1[0..6]}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def irc_gollum_summary_message + summary_message + end + + def branch_name_matches? + return true if data['branches'].nil? + return true if data['branches'].strip == "" + data['branches'].split(',').include?(branch_name) + end +end diff --git a/lib/services/irker.rb b/lib/services/irker.rb new file mode 100644 index 000000000..988b0cee3 --- /dev/null +++ b/lib/services/irker.rb @@ -0,0 +1,103 @@ +class Service::Irker < Service + string :address, :project, :branch, :module, :channels + boolean :long_url, :color, :full_commits + white_list :address, :project, :branch, :module, :channels, :long_url, :color, + :full_commits + + url 'http://www.catb.org/~esr/irker/' + logo_url 'http://www.catb.org/~esr/irker/irker-logo.png' + maintained_by :github => 'AI0867' + supported_by :web => 'irc://chat.freenode.net/#irker' + + require 'json' + + def receive_push + repository = + if !(name = data['project'].to_s).empty? + name + else + payload['repository']['name'] + end + + branch = + if !(branch = data['branch'].to_s).empty? + branch % branch_name + else + ref_name + end + + module_name = data['module'].to_s + + commits = payload['commits'] + + commits.each do |commit| + sha1 = commit['id'] + messages = build_irker_commit(repository, branch, sha1, commit, module_name) + messages.each do |message| + deliver(message) + end + end + end + + attr_writer :irker_con + def irker_con + @irker_con ||= begin + address = data['address'].to_s + if address.empty? + address = "localhost" + end + TCPSocket.new address, 6659 + end + end + + def deliver(message) + irker_con.puts(message) + end + + def build_irker_commit(repository, branch, sha1, commit, module_name) + log_lines = commit['message'].split("\n") + + files = commit['modified'] + commit['added'] + commit['removed'] + tiny_url = config_boolean_true?('long_url') ? commit['url'] : shorten_url(commit['url']) + channels = data['channels'].split(";") + + if config_boolean_true?('color') then + bold = "\x02" + green = "\x0303" + yellow = "\x0307" + brown = "\x0305" + reset = "\x0F" + else + bold = green = yellow = brown = reset = '' + end + + files.uniq! + file_string = files.join(",") + if file_string.size > 80 and files.size > 1 + prefix = files[0] + files.each do |file| + prefix = prefix.rpartition("/")[0] until file.match prefix + end + file_string = "#{prefix}/ (#{files.size} files)" + end + + messages = [] + if config_boolean_true?('full_commits') + privmsg = <<-PRIVMSG +#{bold}#{repository}:#{reset} #{green}#{commit['author']['name']}#{reset} #{module_name}:#{yellow}#{branch}#{reset} * #{bold}#{sha1[0..6]}#{reset} / #{bold}#{file_string}#{reset}: #{brown}#{tiny_url}#{reset} + PRIVMSG + log_lines[0..4].each do |log_line| + privmsg << <<-PRIVMSG +#{bold}#{repository}:#{reset} #{log_line[0..400]} + PRIVMSG + end + messages.push generate_json({'to' => channels, 'privmsg' => privmsg.strip}) + else + privmsg = <<-PRIVMSG +#{bold}#{repository}:#{reset} #{green}#{commit['author']['name']}#{reset} #{module_name}:#{yellow}#{branch}#{reset} * #{bold}#{sha1[0..6]}#{reset} / #{bold}#{file_string}#{reset}: #{log_lines[0][0..300]} #{brown}#{tiny_url}#{reset} + PRIVMSG + messages.push generate_json({'to' => channels, 'privmsg' => privmsg.strip}) + end + return messages + end +end diff --git a/lib/services/iron_mq.rb b/lib/services/iron_mq.rb new file mode 100644 index 000000000..c7030e37d --- /dev/null +++ b/lib/services/iron_mq.rb @@ -0,0 +1,58 @@ +class Service::IronMQ < Service + password :token + string :project_id + string :queue_name + white_list :project_id, :queue_name + + default_events :commit_comment, :download, :fork, :fork_apply, :gollum, + :issues, :issue_comment, :member, :public, :pull_request, :push, :watch + + url "http://iron.io" + logo_url "http://www.iron.io/assets/resources/mq/ironmq-logo-290x160.png" + + # Technoweenie on GitHub is pinged for any bugs with the Hook code. + maintained_by :github => 'treeder' + + # Support channels for user-level Hook problems (service failure, + # misconfigured + supported_by :web => 'http://support.iron.io', + :email => 'support@iron.io' + + def receive_event + #puts "IN PUSH" + #p data + #puts "payload:" + #p payload + + # make sure we have what we need + token = data['token'].to_s.strip + project_id = data['project_id'].to_s.strip + queue_name = data['queue_name'].to_s.strip + raise_config_error "Missing 'token'" if token == '' + raise_config_error "Missing 'project_id'" if project_id == '' + queue_name = queue_name != '' ? queue_name : "github_service_hooks" + + #http.ssl[:verify] = false + body = { + "messages" => [ + "body" => generate_json(payload) + ] + } + + if project_id == '111122223333444455556666' + # test + resp = DumbResponse.new + else + http_post " https://mq-aws-us-east-1.iron.io/1/projects/#{project_id}/queues/#{queue_name}/messages", generate_json(body), {"Authorization" => "OAuth #{token}"} + end + + return data, payload, resp + + end +end + +class DumbResponse + def code + 200 + end +end diff --git a/lib/services/iron_worker.rb b/lib/services/iron_worker.rb new file mode 100644 index 000000000..8a8f672f8 --- /dev/null +++ b/lib/services/iron_worker.rb @@ -0,0 +1,64 @@ +class Service::IronWorker < Service::HttpPost + password :token + string :project_id + string :code_name + white_list :project_id, :code_name + + default_events :commit_comment, :download, :fork, :fork_apply, :gollum, + :issues, :issue_comment, :member, :public, :pull_request, :push, :watch + + url "http://iron.io" + logo_url "http://www.iron.io/assets/resources/worker/ironworker-logo-290x160.png" + + # Technoweenie on GitHub is pinged for any bugs with the Hook code. + maintained_by :github => "treeder" # I'm happy to get pinged for bugs, but feel free to remove me and add Iron.io staff. + + # Support channels for user-level Hook problems (service failure, + # misconfigured + supported_by :web => 'http://get.iron.io/chat', + :email => 'support@iron.io' + + def receive_event + #puts "IN PUSH" + #p data + #puts "payload:" + #p payload + + # make sure we have what we need + token = data['token'].to_s.strip + project_id = data['project_id'].to_s.strip + code_name = data['code_name'].to_s.strip + + raise_config_error "Missing 'token'" if token == '' + raise_config_error "Missing 'project_id'" if project_id == '' + raise_config_error "Missing 'code_name'" if code_name == '' + + #http.ssl[:verify] = false + #body = { + # "messages" => [ + # "body" => payload.to_json + # ] + #} + + if project_id == '111122223333444455556666' + # test + resp = DumbResponse.new + else + url = iron_worker_webhook_url(project_id, token, code_name) + http_post url, JSON.generate(payload) + end + + return data, payload, resp + end + + def iron_worker_webhook_url(project_id, token, code_name) + worker_api_url = "https://worker-aws-us-east-1.iron.io/2" + "#{worker_api_url}/projects/#{project_id}/tasks/webhook?code_name=#{code_name}&oauth=#{token}" + end +end + +class DumbResponse + def code + 200 + end +end diff --git a/lib/services/jabber.rb b/lib/services/jabber.rb new file mode 100644 index 000000000..6445b1b8a --- /dev/null +++ b/lib/services/jabber.rb @@ -0,0 +1,56 @@ +class Service::Jabber < Service + string :user + white_list :user + + def receive_push + #Split multiple addresses into array, removing duplicates + recipients = data.has_key?('user') ? data['user'].split(',').each(&:strip!).uniq : [] + messages = [] + messages << "#{summary_message}: #{summary_url}" + messages += commit_messages + message = messages.join("\n") + + deliver_messages(message, recipients) + end + + def deliver_messages(message, recipients) + m = ::Jabber::Message.new(nil, message).set_type(:chat) + recipients.each do |recipient| + m.set_to(recipient) + client.send(m) + end + disconnect + end + + def client + @client ||= begin + user = secrets['jabber']['user'].to_s + pass = secrets['jabber']['password'].to_s + + if user.empty? || pass.empty? + raise_config_error("Missing Jabber user/pass: #{user.inspect}") + end + + jid = ::Jabber::JID.new(user) + client = ::Jabber::Client.new(jid) + client.connect + client.auth(pass) + + roster = ::Jabber::Roster::Helper.new(client, false) + roster.add_subscription_request_callback do |roster_item, presence| + roster.accept_subscription(presence.from) + end + + presence = ::Jabber::Presence.new(nil, "Available") + client.send(presence) + client.send_with_id(::Jabber::Iq.new_rosterget) + client + end + end + + def disconnect + client.close + @client = nil + end + +end diff --git a/services/jaconda.rb b/lib/services/jaconda.rb similarity index 84% rename from services/jaconda.rb rename to lib/services/jaconda.rb index dba61f657..66f536c63 100644 --- a/services/jaconda.rb +++ b/lib/services/jaconda.rb @@ -1,6 +1,8 @@ class Service::Jaconda < Service - string :subdomain, :room_id, :room_token + string :subdomain, :room_id + password :room_token boolean :digest + white_list :subdomain, :room_id default_events :commit_comment, :download, :fork, :fork_apply, :gollum, :issues, :issue_comment, :member, :public, :pull_request, :push, :watch @@ -12,9 +14,8 @@ def receive_event http.basic_auth data['room_token'], 'X' res = http_post "https://#{data['subdomain']}.jaconda.im/api/v2/rooms/#{data['room_id']}/notify/github.json", - :payload => JSON.generate(payload), + :payload => generate_json(payload), :digest => data['digest'], - :branch_name => branch_name, :event => event if res.status < 200 || res.status > 299 diff --git a/lib/services/jenkins_git.rb b/lib/services/jenkins_git.rb new file mode 100644 index 000000000..c1b2e376a --- /dev/null +++ b/lib/services/jenkins_git.rb @@ -0,0 +1,27 @@ +class Service::JenkinsGit < Service + self.title = 'Jenkins (Git plugin)' + + NOTIFY_URL = '%s/git/notifyCommit' + + string :jenkins_url + white_list :jenkins_url + + def receive_push + if !data['jenkins_url'].present? + raise_config_error "Jenkins URL not set" + end + + url = NOTIFY_URL % data['jenkins_url'].sub(%r{/+$}, '') + + params = { + :url => repo_url, + :from => 'github' + } + + params[:branches] = branch_name unless tag? + + http.ssl[:verify] = false + http.url_prefix = url + http_get url, params + end +end diff --git a/lib/services/jenkins_github.rb b/lib/services/jenkins_github.rb new file mode 100644 index 000000000..a6955c112 --- /dev/null +++ b/lib/services/jenkins_github.rb @@ -0,0 +1,20 @@ +class Service::JenkinsGitHub < Service + self.title = 'Jenkins (GitHub plugin)' + self.hook_name = 'jenkins' # legacy hook name + + string :jenkins_hook_url + white_list :jenkins_hook_url + + def receive_push + if data['jenkins_hook_url'].present? + url = data['jenkins_hook_url'] + else + raise_config_error "Jenkins Hook Url not set" + end + http.ssl[:verify] = false # :( + http.url_prefix = url + http.headers['X-GitHub-Event'] = "push" + http_post url, + :payload => generate_json(payload) + end +end diff --git a/services/jira.rb b/lib/services/jira.rb similarity index 84% rename from services/jira.rb rename to lib/services/jira.rb index 2d6f42af2..f33171d05 100644 --- a/services/jira.rb +++ b/lib/services/jira.rb @@ -1,6 +1,7 @@ -class Service::Jira < Service +class Service::JIRA < Service string :server_url, :api_version, :username password :password + white_list :api_version, :server_url, :username def receive_push payload['commits'].each do |commit| @@ -21,9 +22,9 @@ def receive_push key, value = entry.split(':') if key =~ /(?i)status|(?i)transition/ - changeset.merge!(:transition => value.to_i) + changeset.merge!(:transition => value.to_s) elsif key =~ /(?i)resolution/ - changeset.merge!(:fields => { :resolution => value.to_i }) + changeset.merge!(:fields => { :resolution => { :id => value.to_s } }) else changeset.merge!(:fields => { key.to_sym => "Resolved" }) end @@ -39,7 +40,7 @@ def receive_push http.basic_auth data['username'], data['password'] http.headers['Content-Type'] = 'application/json' res = http_post '%s/rest/api/%s/issue/%s/transitions' % [data['server_url'], data['api_version'], issue_id], - changeset.to_json + generate_json(changeset) rescue URI::InvalidURIError raise_config_error "Invalid server_hostname: #{data['server_url']}" end diff --git a/services/kanbanery.rb b/lib/services/kanbanery.rb similarity index 55% rename from services/kanbanery.rb rename to lib/services/kanbanery.rb index 79924c2a1..973cd1b5b 100644 --- a/services/kanbanery.rb +++ b/lib/services/kanbanery.rb @@ -1,15 +1,16 @@ class Service::Kanbanery < Service - string :project_id, :project_token + string :project_id + password :project_token + white_list :project_id def receive_push project_id = data['project_id'] token = data['project_token'] - http_post "http://kanbanery.com/api/v1/projects/#{project_id}/git_commits", - payload.to_json, + http_post "https://kanbanery.com/api/v1/projects/#{project_id}/git_commits", + generate_json(payload), 'X-Kanbanery-ProjectGitHubToken' => token, 'Content-Type' => 'application/json' - end end diff --git a/lib/services/kanbanize.rb b/lib/services/kanbanize.rb new file mode 100644 index 000000000..7354084c3 --- /dev/null +++ b/lib/services/kanbanize.rb @@ -0,0 +1,48 @@ +class Service::Kanbanize < Service + string :kanbanize_domain_name + string :kanbanize_api_key + string :restrict_to_branch + boolean :restrict_to_last_commit + boolean :track_project_issues_in_kanbanize + string :project_issues_board_id + + # Skip the api key from the debug logs. + white_list :kanbanize_domain_name, :restrict_to_branch, :restrict_to_last_comment, :track_project_issues_in_kanbanize, :project_issues_board_id + + default_events :push, :issues, :issue_comment + + url "https://kanbanize.com/" + logo_url "https://kanbanize.com/application/resources/images/logo.png" + + maintained_by :github => 'DanielDraganov' + supported_by :email => 'office@kanbanize.com' + + def receive_event + # Make sure that the api key is provided. + raise_config_error "Missing 'kanbanize_api_key'" if data['kanbanize_api_key'].to_s == '' + + domain_name = data['kanbanize_domain_name'] + api_key = data['kanbanize_api_key'] + branch_restriction = data['restrict_to_branch'].to_s + commit_restriction = config_boolean_true?('restrict_to_last_commit') + issues_tracking = config_boolean_true?('track_project_issues_in_kanbanize') + issues_board_id = data['project_issues_board_id'].to_s + + # check the branch restriction is poplulated and branch is not included + if event.to_s == 'push' + branch = payload['ref'].split('/').last + if branch_restriction.length > 0 && branch_restriction.index(branch) == nil + return + end + end + + http_post "http://#{domain_name}/index.php/api/kanbanize/git_hub_event", + generate_json(payload), + 'apikey' => api_key, + 'branch-filter' => branch_restriction, + 'last-commit' => commit_restriction, + 'track-issues' => issues_tracking, + 'board-id' => issues_board_id, + 'Content-Type' => 'application/json' + end +end diff --git a/lib/services/landscape.rb b/lib/services/landscape.rb new file mode 100644 index 000000000..ca17ba1ef --- /dev/null +++ b/lib/services/landscape.rb @@ -0,0 +1,17 @@ +class Service::Landscape < Service::HttpPost + default_events Service::ALL_EVENTS + + url "https://landscape.io" + logo_url "https://landscape-io.s3.amazonaws.com/img/landscape_logo.png" + + maintained_by :github => 'landscapeio' + + supported_by :web => 'https://landscape.io/contact', + :email => 'help@landscape.io', + :twitter => 'landscapeio', + :github => 'landscapeio' + + def receive_event + deliver 'https://landscape.io/hooks/github' + end +end diff --git a/lib/services/leanpub.rb b/lib/services/leanpub.rb new file mode 100644 index 000000000..5de89a7ae --- /dev/null +++ b/lib/services/leanpub.rb @@ -0,0 +1,34 @@ +class Service::Leanpub < Service::HttpPost + string :api_key, :slug + + white_list :slug + + default_events :push + + url "https://leanpub.com" + logo_url "https://leanpub.com/assets/leanpub_logo_small.png" + + maintained_by :github => 'spatten', + :email => 'scott@leanpub.com', + :twitter => '@scott_patten' + + supported_by :web => 'https://leanpub.com/contact', + :email => 'hello@leanpub.com', + :twitter => '@leanpub' + + def receive_event + slug = required_config_value('slug') + api_key = required_config_value('api_key') + + if api_key.match(/^[A-Za-z0-9_-]+$/) == nil + raise_config_error "Invalid api key" + end + + if slug.match(/^[A-Za-z0-9_-]+$/) == nil + raise_config_error "Invalid slug" + end + + url = "https://leanpub.com:443/#{slug}/preview?api_key=#{api_key}" + deliver url + end +end diff --git a/services/lighthouse.rb b/lib/services/lighthouse.rb similarity index 77% rename from services/lighthouse.rb rename to lib/services/lighthouse.rb index e217a05c2..3268ea779 100644 --- a/services/lighthouse.rb +++ b/lib/services/lighthouse.rb @@ -1,14 +1,17 @@ class Service::Lighthouse < Service - string :subdomain, :project_id, :token + string :subdomain, :project_id + password :token boolean :private, :send_only_ticket_commits + white_list :subdomain, :project_id def receive_push # matches string with square braces with content starting with # and a digit. check_for_lighthouse_flags = /\[#\d.+?\]/ - + payload['commits'].each do |commit| next if commit['message'] =~ /^x / - next if data['send_only_ticket_commits'] == false && (commit['message'] =~ check_for_lighthouse_flags).nil? + next if config_boolean_true?('send_only_ticket_commits') \ + && (commit['message'] =~ check_for_lighthouse_flags).nil? commit_id = commit['id'] added = commit['added'].map { |f| ['A', f] } @@ -16,7 +19,7 @@ def receive_push modified = commit['modified'].map { |f| ['M', f] } diff = YAML.dump(added + removed + modified) - diff = YAML.dump([]) if data['private'] + diff = YAML.dump([]) if config_boolean_true?('private') title = "Changeset [%s] by %s" % [commit_id, commit['author']['name']] body = "#{commit['message']}\n#{commit['url']}" @@ -31,6 +34,8 @@ def receive_push XML + @lighthouse_body = changeset_xml + account = "http://#{data['subdomain']}.lighthouseapp.com" begin @@ -44,4 +49,11 @@ def receive_push end end end + + def reportable_http_env(env, time) + hash = super(env, time) + hash[:request][:body] = @lighthouse_body + @lighthouse_body = nil + hash + end end diff --git a/lib/services/lingohub.rb b/lib/services/lingohub.rb new file mode 100644 index 000000000..e7e87415b --- /dev/null +++ b/lib/services/lingohub.rb @@ -0,0 +1,28 @@ +class Service::Lingohub < Service + + url "https://lingohub.com" + logo_url "https://lingohub.com/assets/public/press/media_kit/lingohub_logo.jpg" + + maintained_by :github => 'lingohub' + + supported_by :web => 'http://support.lingohub.com', + :email => 'support@lingohub.com' + + default_events :push + password :project_token + + def receive_push + project_token = data['project_token'] + + if project_token.nil? + raise_config_error "You have to specify a Project Token" + end + + res = http_post "http://lingohub.com/github_callback?auth_token=#{project_token}", + :payload => generate_json(payload) + + if res.status < 200 || res.status > 299 + raise_config_error res.body + end + end +end diff --git a/services/mantis_bt.rb b/lib/services/mantis_bt.rb old mode 100755 new mode 100644 similarity index 82% rename from services/mantis_bt.rb rename to lib/services/mantis_bt.rb index 7f94d9e3d..f46adb595 --- a/services/mantis_bt.rb +++ b/lib/services/mantis_bt.rb @@ -1,10 +1,11 @@ class Service::MantisBT < Service string :url, :api_key + white_list :url def receive_push http.ssl[:verify] = false http.url_prefix = data['url'] - res = http_post 'plugin.php', :payload => payload.to_json do |req| + res = http_post 'plugin.php', :payload => generate_json(payload) do |req| req.params.update \ :page => "Source/checkin", :api_key => data['api_key'] diff --git a/lib/services/maxcdn.rb b/lib/services/maxcdn.rb new file mode 100644 index 000000000..354ea11b2 --- /dev/null +++ b/lib/services/maxcdn.rb @@ -0,0 +1,66 @@ +require "maxcdn" + +class Service::MaxCDN < Service + + STATIC_EXTENSIONS = [ + :css, :js, :jpg, :jpeg, :gif, :ico, :png, :bmp, + :pict, :csv, :doc, :pdf, :pls, :ppt, :tif, :tiff, + :eps, :ejs, :swf, :midi, :mid, :txt, :ttf, :eot, + :woff, :otf, :svg, :svgz, :webp, :docx, :xlsx, + :xls, :pptx, :ps, :rss, :class, :jar + ].freeze + + string :alias, + :key, + :secret, + :zone_id + + boolean :static_only + + url "http://docs.maxcdn.com/" + logo_url "http://www.maxcdn.com/wp-content/themes/maxcdnv4/img/png/maxcdn-colored-logo.png" + + maintained_by :github => "jmervine", + :email => "joshua@mervine.net", + :twitter => "@mervinej" + + supported_by :web => "http://support.maxcdn.com/", + :email => "support@maxcdn.com", + :twitter => "@MaxCDN" + + def receive_push + return unless payload["commits"] + return if config_boolean_true?("static_only") and !has_static? + + begin + maxcdn.purge data["zone_id"] + rescue ::MaxCDN::APIException => e + raise_config_error(e.message) + end + end + + def modified_files + payload["commits"].map { |commit| commit["modified"] }.flatten!.uniq! + end + + def extensions + ::Service::MaxCDN::STATIC_EXTENSIONS + end + + def has_static? + files = modified_files.clone.select! do |file| + matched = false + extensions.each do |ext| + matched = true if /#{ext}/.match(file) + end + matched + end + + (files.size > 0) + end + + def maxcdn + @maxcdn ||= ::MaxCDN::Client.new(data["alias"], data["key"], data["secret"]) + end +end + diff --git a/lib/services/mqtt.rb b/lib/services/mqtt.rb new file mode 100644 index 000000000..550b01c05 --- /dev/null +++ b/lib/services/mqtt.rb @@ -0,0 +1,75 @@ +class Service::MqttPub < Service + self.title = 'MQTT publish' + + string :broker, :port, :topic, :clientid, :user + password :pass + boolean :retain + + require 'mqtt' + + def receive_push + + # Optional - use m2m.io public broker if not specified + broker = data['broker'].to_s + if broker == '' + broker = 'q.m2m.io' + end + + # Optional - use standard MQTT port if not specified + port = data['port'].to_i + if data['port'].to_s == '' + port = 1883 + end + + if data['topic'].to_s == '' + raise_config_error "Invalid topic. Try github// ." + end + + # Optional - generate random epoch for ID if not specified + clientid = data['clientid'].to_s + if clientid == '' + # Random ID doesn't make sense, but use prefix like MQTT::generate_client_id + clientid = 'github_' + Time.now.to_i.to_s + end + + # Optional, specify nil if not specified (per http://rubydoc.info/gems/mqtt/MQTT/Client) + user = data['user'].to_s + if user == '' + user = nil + end + + # Optional, specify nil if not specified + pass = data['pass'].to_s + if pass == '' + pass = nil + end + + # Handle specifying a username or a password, but not both + if user != nil and pass == nil + raise_config_error "You specified a username without a password." + end + + if pass != nil and user == nil + raise_config_error "You specified a password without a username." + end + + begin + # Connect to the broker, publish the payload! + MQTT::Client.connect( + :remote_host => broker, + :remote_port => port, + :client_id => clientid, + :username => user, + :password => pass + ) do |client| + client.publish(data['topic'].to_s, generate_json(payload), retain=data['retain']) + # Disconnect (don't send last will and testament) + client.disconnect(false) + end + rescue SocketError => e + warn "SocketError occurred: " + e + end + + end +end + diff --git a/lib/services/myget.rb b/lib/services/myget.rb new file mode 100644 index 000000000..e8489527e --- /dev/null +++ b/lib/services/myget.rb @@ -0,0 +1,23 @@ +class Service::MyGet < Service::HttpPost + string :hook_url + + white_list :hook_url + + url "https://www.myget.org" + logo_url "https://www.myget.org/Content/images/myget/myget_125x25.png" + + default_events :push + + maintained_by :github => 'myget', + :twitter => '@MyGetTeam' + + supported_by :web => 'https://www.myget.org/support', + :email => 'support@myget.org', + :twitter => '@MyGetTeam' + + def receive_event + url = required_config_value('hook_url') + + deliver url + end +end diff --git a/lib/services/obs.rb b/lib/services/obs.rb new file mode 100644 index 000000000..f3f08d635 --- /dev/null +++ b/lib/services/obs.rb @@ -0,0 +1,53 @@ +class Service::Obs < Service::HttpPost + string :url, :project, :package, :refs + password :token + + white_list :url, :project, :package, :refs + + default_events :push + + url "https://build.opensuse.org" + logo_url "https://github.com/openSUSE/obs-landing/blob/gh-pages/images/obs-logo.png" + + maintained_by :github => 'adrianschroeter' + + supported_by :web => 'https://www.openbuildservice.org/support/', + :email => 'buildservice-opensuse@opensuse.org' + + def receive_push + # required + token = required_config_value('token').to_s + apiurl = config_value('url') + apiurl = "https://api.opensuse.org:443" if apiurl.blank? + + # optional. The token may set the package container already. + project = config_value('project') + package = config_value('package') + + # optional. Do not filter references by default. + refs = config_value('refs') + + if refs.present? + return unless refs.split(":").any? do |pattern| + File::fnmatch(pattern, ref) + end + end + + # multiple tokens? handle each one individually + token.split(",").each do |t| + # token is not base64 + if t.strip.match(/^[A-Za-z0-9+\/=]+$/) == nil + raise_config_error "Invalid token" + end + + http.ssl[:verify] = false + http.headers['Authorization'] = "Token #{t.strip}" + + url = "#{apiurl}/trigger/runservice" + unless project.blank? or package.blank? + url << "?project=#{CGI.escape(project)}&package=#{CGI.escape(package)}" + end + deliver url + end + end +end diff --git a/lib/services/ontime.rb b/lib/services/ontime.rb new file mode 100644 index 000000000..6c9a29ae3 --- /dev/null +++ b/lib/services/ontime.rb @@ -0,0 +1,43 @@ +class Service::OnTime < Service + string :ontime_url, :api_key + white_list :ontime_url + + self.title = 'OnTime' + + def receive_push + raise_config_error 'No OnTime URL to connect to.' if data['ontime_url'].to_s.empty? + raise_config_error 'No API Key.' if data['api_key'].to_s.empty? + + http.url_prefix = data['ontime_url'] + json = generate_json(payload) + resp = http_get 'api/version' + version = JSON.parse(resp.body)['data'] + curvers = "#{version['major']}.#{version['minor]']}" + + # Hash the data, it has to be hexdigest in order to have the same hash value in .NET + hash_data = Digest::SHA256.hexdigest(json + data['api_key']) + + if (version['major'] == 11 and version['minor'] >= 1) or (version['major'] == 12 and version['minor'] < 2) + result = http_post 'api/github', :payload => json, :hash_data => hash_data, :source => :github, :service_version => 1.0, :ontime_version => curvers + elsif (version['major'] == 12 and version['minor'] >= 2) or (version['major'] == 13 and version['minor'] < 3) + result = http_post 'api/v1/github', :payload => json, :hash_data => hash_data, :source => :github, :service_version => 1.1, :ontime_version => curvers + elsif (version['major'] == 13 and version['minor'] >= 3) or version['major'] > 13 + http.headers['Content-Type'] = 'application/json' + result = http_post("api/v2/github?hash_data=#{hash_data}&ontime_version=#{curvers}&service_version=2.0", json) + else + raise_config_error 'Unexpected API version. Please update to the latest version of OnTime to use this service.' + end + + verify_response(result) + end + + def verify_response(res) + case res.status + when 200..299 + when 403, 401, 422 then raise_config_error("Invalid Credentials") + when 404, 301, 302 then raise_config_error("Invalid URL") + else raise_config_error("HTTP: #{res.status}") + end + end +end + diff --git a/lib/services/packagist.rb b/lib/services/packagist.rb new file mode 100644 index 000000000..f323ef0b3 --- /dev/null +++ b/lib/services/packagist.rb @@ -0,0 +1,52 @@ +class Service::Packagist < Service + string :user + password :token + string :domain + white_list :domain, :user + + def receive_push + http_post packagist_url, :payload => generate_json(payload), :username => user, :apiToken => token + end + + def packagist_url + "#{scheme}://#{domain}/api/github" + end + + def user + if data['user'].to_s == '' + payload['repository']['owner']['name'] + else + data['user'] + end.strip + end + + def token + if data['token'].to_s == '' + '' + else + data['token'].strip + end + end + + def scheme + domain_parts.size == 1 ? 'http' : domain_parts.first + end + + def domain + domain_parts.last + end + + protected + + def full_domain + if data['domain'].to_s == '' + 'https://packagist.org' + else + data['domain'].lstrip.sub(/[\/\s]+\z/,'').sub(/\Ahttp:\/\/packagist.org/, 'https://packagist.org') + end + end + + def domain_parts + @domain_parts ||= full_domain.split('://') + end +end diff --git a/lib/services/phraseapp.rb b/lib/services/phraseapp.rb new file mode 100644 index 000000000..b859514ff --- /dev/null +++ b/lib/services/phraseapp.rb @@ -0,0 +1,40 @@ +class Service::Phraseapp < Service::HttpPost + title "PhraseApp" + + password :auth_token + + white_list :auth_token + + default_events :push + + url "https://phraseapp.com" + logo_url "https://phraseapp.com/assets/github/phraseapp-logo.png" + + maintained_by github: "docstun" + + supported_by web: "https://phraseapp.com/contact", + email: "info@phraseapp.com", + twitter: "@phraseapp" + + def receive_push + auth_token = required_config_value("auth_token") + raise_config_error "Invalid auth token" unless auth_token.match(/^[A-Za-z0-9]+$/) + + body = generate_json(hook_params) + http_post(hook_uri, body) do |request| + request.params[:auth_token] = auth_token + end + end + +protected + def hook_uri + "https://phraseapp.com:443/api/v1/hooks/github" + end + + def hook_params + { + data: data, + payload: payload + } + end +end diff --git a/services/pivotal_tracker.rb b/lib/services/pivotal_tracker.rb similarity index 81% rename from services/pivotal_tracker.rb rename to lib/services/pivotal_tracker.rb index 0dd98b96e..63c757ffe 100644 --- a/services/pivotal_tracker.rb +++ b/lib/services/pivotal_tracker.rb @@ -1,5 +1,7 @@ class Service::PivotalTracker < Service - string :token, :branch, :endpoint + password :token + string :branch, :endpoint + white_list :endpoint, :branch def receive_push token = data['token'] @@ -14,7 +16,7 @@ def notify endpoint = 'https://www.pivotaltracker.com/services/v3/github_commits' if endpoint.empty? res = http_post endpoint do |req| req.params[:token] = data['token'] - req.body = {:payload => payload.to_json} + req.body = {:payload => generate_json(payload)} end if res.status < 200 || res.status > 299 diff --git a/lib/services/piwik_plugins.rb b/lib/services/piwik_plugins.rb new file mode 100644 index 000000000..8fdbbad71 --- /dev/null +++ b/lib/services/piwik_plugins.rb @@ -0,0 +1,17 @@ +class Service::PiwikPlugins < Service::HttpPost + + self.title = 'Piwik Plugins' + + url "http://plugins.piwik.org/" + + logo_url "http://piwik.org/wp-content/themes/piwik/img/logo_mainpage.png" + + maintained_by :github => 'halfdan', :twitter => 'geekproject' + + supported_by :email => 'fabian@piwik.org' + + def receive_push + deliver "http://plugins.piwik.org/postreceive-hook" + end + +end diff --git a/services/planbox.rb b/lib/services/planbox.rb similarity index 59% rename from services/planbox.rb rename to lib/services/planbox.rb index c081eac03..1af238eb3 100644 --- a/services/planbox.rb +++ b/lib/services/planbox.rb @@ -1,11 +1,11 @@ class Service::Planbox < Service - string :token + password :token def receive_push token = data['token'] - res = http_post 'https://www.planbox.com/api/github_commits' do |req| + res = http_post 'https://work.planbox.com/api/github_commits' do |req| req.params[:token] = data['token'] - req.body = {:payload => payload.to_json} + req.body = {:payload => generate_json(payload)} end if res.status < 200 || res.status > 299 diff --git a/services/redmine.rb b/lib/services/planio.rb similarity index 72% rename from services/redmine.rb rename to lib/services/planio.rb index b854e24c1..cfb7f3181 100644 --- a/services/redmine.rb +++ b/lib/services/planio.rb @@ -1,8 +1,9 @@ -class Service::Redmine < Service +class Service::Planio < Service string :address, :project, :api_key + white_list :address, :project def receive_push - http.ssl[:verify] = false + http.ssl[:verify] = true http.url_prefix = data['address'] http_get "sys/fetch_changesets" do |req| req.params['key'] = data['api_key'] diff --git a/services/prowl.rb b/lib/services/prowl.rb similarity index 99% rename from services/prowl.rb rename to lib/services/prowl.rb index 185c8b305..c23691d41 100644 --- a/services/prowl.rb +++ b/lib/services/prowl.rb @@ -8,7 +8,7 @@ def receive_push event = [repository[-2], repository[-1]].join('/') application = "GitHub" description = "#{payload['commits'].length} commits pushed to #{application} (#{payload['commits'][-1]['id'][0..7]}..#{payload['commits'][0]['id'][0..7]}) - + Latest Commit by #{payload['commits'][-1]['author']['name']} #{payload['commits'][-1]['id'][0..7]} #{payload['commits'][-1]['message']}" diff --git a/lib/services/pushbullet.rb b/lib/services/pushbullet.rb new file mode 100644 index 000000000..c2460774d --- /dev/null +++ b/lib/services/pushbullet.rb @@ -0,0 +1,26 @@ +class Service::Pushbullet < Service::HttpPost + string :api_key, :device_iden + + default_events :push, :issues, :pull_request + + url "https://www.pushbullet.com/" + logo_url "https://www.pushbullet.com/img/header-logo.png" + + maintained_by :github => 'tuhoojabotti', + :twitter => 'tuhoojabotti', + :web => 'http://tuhoojabotti.com/#contact' + + supported_by :web => 'https://www.pushbullet.com/help', + :email => 'hey@pushbullet.com' + + def receive_event + unless required_config_value('api_key').match(/^[A-Za-z0-9\.]+$/) + raise_config_error "Invalid api key." + end + unless config_value('device_iden').match(/^([A-Za-z0-9]+|)$/) + raise_config_error "Invalid device iden." + end + + deliver "https://webhook.pushbullet.com:443/github" + end +end diff --git a/lib/services/pushover.rb b/lib/services/pushover.rb new file mode 100644 index 000000000..a95381a3c --- /dev/null +++ b/lib/services/pushover.rb @@ -0,0 +1,42 @@ +class Service::Pushover < Service + string :user_key, :device_name + white_list :device_name + + def receive_push + if !payload["commits"].any? + return + end + + if !data["user_key"] + raise_config_error "Invalid Pushover user key." + end + + url = URI.parse("https://api.pushover.net/1/messages.json") + + commits = payload["commits"].length + repo = payload["repository"]["url"].split("/")[-2 .. -1].join("/") + latest_message = payload["commits"].last["message"].split("\n").first + if latest_message.length > 100 + latest_message = latest_message[0 ... 96] + "[..]" + end + latest_url = shorten_url(payload["commits"].last["url"]) + + if commits == 1 + title = "#{payload["pusher"]["name"]} pushed to #{repo}" + message = latest_message + else + title = "#{payload["pusher"]["name"]} pushed #{commits} " + + "commit#{commits == 1 ? '' : 's'} to #{repo}" + message = "Latest: #{latest_message}" + end + + http_post url.to_s, + :token => "E6jpcJjaeASA7CWQ0cYjW6oP9N5xtJ", + :user => data["user_key"], + :device => data["device_name"], + :title => title, + :message => message, + :url => latest_url, + :url_title => "View commit on GitHub" + end +end diff --git a/lib/services/rally.rb b/lib/services/rally.rb new file mode 100644 index 000000000..6d5ee0f0e --- /dev/null +++ b/lib/services/rally.rb @@ -0,0 +1,186 @@ +require 'time' + +class Service::Rally < Service + string :server, :username, :workspace, :repository + password :password + white_list :server, :workspace, :repository + + attr_accessor :wksp_ref, :user_cache + + def receive_push + server = data['server'] + username = data['username'] + password = data['password'] + workspace = data['workspace'] + scm_repository = data['repository'] + raise_config_error("No Server value specified") if server.nil? or server.strip.length == 0 + raise_config_error("No UserName value specified") if username.nil? or username.strip.length == 0 + raise_config_error("No Password value specified") if password.nil? or password.strip.length == 0 + raise_config_error("No Workspace value specified") if workspace.nil? or workspace.strip.length == 0 + branch = payload['ref'].split('/')[-1] # most of the time it'll be refs/heads/master ==> master + repo = payload['repository']['name'] + repo_uri = payload['repository']['url'] + + http.ssl[:verify] = false + if server =~ /^https?:\/\// # if they have http:// or https://, leave server value unchanged + http.url_prefix = "#{server}/slm/webservice/1.30" + else + server = "#{server}.rallydev.com" if server !~ /\./ # leave unchanged if '.' in server + http.url_prefix = "https://#{server}/slm/webservice/1.30" + end + http.basic_auth(username, password) + http.headers['Content-Type'] = 'application/json' + http.headers['X-RallyIntegrationVendor'] = 'Rally' + http.headers['X-RallyIntegrationName'] = 'GitHub-Service' + http.headers['X-RallyIntegrationVersion'] = '1.1' + + # create the repo in Rally if it doesn't already exist + @wksp_ref = validateWorkspace(workspace) + repo_ref = getOrCreateRepo(scm_repository, repo, repo_uri) + @user_cache = {} + payload['commits'].each do |commit| + artifact_refs = snarfArtifacts(commit['message']) + addChangeset(commit, repo_ref, artifact_refs, repo_uri, branch) + end + end + + def addChangeset(commit, repo_ref, artifact_refs, repo_uri, branch) + author = commit['author']['email'] + if !@user_cache.has_key?(author) + user = rallyQuery('User', 'Name,UserName', 'UserName = "%s"' % [author]) + user_ref = "" + user_ref = itemRef(user) unless user.nil? + @user_cache[author] = user_ref + end + + user_ref = @user_cache[author] + message = commit['message'][0..3999] # message max size is 4000 characters + changeset = { + 'SCMRepository' => repo_ref, + 'Revision' => commit['id'], + 'CommitTimestamp' => Time.iso8601(commit['timestamp']).strftime("%FT%H:%M:%S.00Z"), + 'Author' => user_ref, + 'Message' => message, + 'Uri' => '%s/commit/%s' % [repo_uri, commit['id']], + 'Artifacts' => artifact_refs # [{'_ref' => 'defect/1324.js'}, {}...] + } + changeset.delete('Author') if user_ref == "" + + begin + changeset_item = rallyCreate('Changeset', changeset) + chgset_ref = itemRef(changeset_item) + rescue Faraday::Error => boom # or some other sort of Faraday::Error::xxxError + raise_config_error("Unable to create Rally Changeset") + # changeset_item = nil + end + + return if changeset_item.nil? + + # change has changeset_ref, Action, PathAndFilename, Uri + changes = [] + commit['added'].each { |add| changes << {'Action' => 'A', 'PathAndFilename' => add } } + commit['modified'].each { |mod| changes << {'Action' => 'M', 'PathAndFilename' => mod } } + commit['removed'].each { |rem| changes << {'Action' => 'R', 'PathAndFilename' => rem } } + changes.each do |change| + change['Changeset'] = chgset_ref + change['Uri'] = '%s/blob/%s/%s' % [repo_uri, branch, change['PathAndFilename']] + chg_item = rallyCreate('Change', change) + end + end + + def validateWorkspace(workspace) + all_your_workspaces = rallyWorkspaces() + target_workspace = all_your_workspaces.select {|wksp| wksp['Name'] == workspace and wksp['State'] != 'Closed'} + if target_workspace.length != 1 + problem = 'Config Error: target workspace %s not available in list of workspaces associated with your credentials' % [workspace] + raise_config_error(problem) + end + + return itemRef(target_workspace[0]) + end + + def getOrCreateRepo(scm_repository, repo, repo_uri) + scm_repository = repo if (scm_repository.nil? or scm_repository == "") + repo_item = rallyQuery('SCMRepository', 'Name', 'Name = "%s"' % scm_repository) + return itemRef(repo_item) unless repo_item.nil? + repo_info = { + 'Workspace' => @wksp_ref, + 'Name' => scm_repository, + 'SCMType' => 'GitHub', + 'Description' => 'GitHub-Service push Changesets', + 'Uri' => '%s' % [repo_uri] + } + repo_item = rallyCreate('SCMRepository', repo_info) + + return itemRef(repo_item) + end + + def itemRef(item) + ref = item['_ref'].split('/')[-2..-1].join('/')[0..-4] + end + + def rallyWorkspaces() + response = http_get('Subscription.js?fetch=Name,Workspaces,Workspace&pretty=true') + raise_config_error('Config error: credentials not valid for Rally endpoint') if response.status == 401 + raise_config_error('Config error: unable to obtain your Rally subscription info') unless response.success? + qr = JSON.parse(response.body) + begin + workspaces = qr['Subscription']['Workspaces'] + rescue Exception => ex + raise_config_error('Config error: no such workspace for your credentials') + end + + return workspaces + end + + def rallyQuery(entity, fields, criteria) + target_url = '%s.js?fetch=%s' % [entity.downcase, fields] + target_url += '&query=(%s)' % [criteria] if criteria.length > 0 + target_url += '&workspace=%s' % [@wksp_ref] + res = http_get(target_url) + raise StandardError("Config Error: #{entity} query failed") unless res.success? + qr = JSON.parse(res.body)['QueryResult'] + item = qr['TotalResultCount'] > 0 ? qr['Results'][0] : nil + + return item + end + + def rallyCreate(entity, data) + create_url = "%s/create.js?workspace=%s" % [entity, @wksp_ref] + payload = {"#{entity}" => data} + res = http_post(create_url,generate_json(payload)) + raise_config_error("Unable to create the Rally #{entity} for #{data['Name']}") unless res.success? + cr = JSON.parse(res.body)['CreateResult'] + item = cr['Object'] + + return item + end + + def snarfArtifacts(message) + art_type = { + 'D' => 'defect', + 'DE' => 'defect', + 'DS' => 'defectsuite', + 'TA' => 'task', + 'TC' => 'testcase', + 'S' => 'hierarchicalrequirement', + 'US' => 'hierarchicalrequirement' + } + formatted_id_pattern = '^(%s)\d+[\.:;]?$' % art_type.keys.join('|') # '^(D|DE|DS|TA|TC|S|US)\d+[\.:;]?$' + artifact_detector = Regexp.compile(formatted_id_pattern) + words = message.gsub(',', ' ').gsub('\r\n', '\n').gsub('\n', ' ').gsub('\t', ' ').split(' ') + rally_formatted_ids = words.select { |word| artifact_detector.match(word) } + artifacts = [] # actually, just the refs + rally_formatted_ids.uniq.each do |fmtid| + next unless fmtid =~ /^(([A-Z]{1,2})\d+)[\.:;]?$/ + fmtid, prefix = $1, $2 + entity = art_type[prefix] + artifact = rallyQuery(entity, 'Name', 'FormattedID = "%s"' % fmtid) + next if artifact.nil? + art_ref = itemRef(artifact) + artifacts << {'_ref' => art_ref} + end + + return artifacts + end +end diff --git a/lib/services/rational_team_concert.rb b/lib/services/rational_team_concert.rb new file mode 100644 index 000000000..8e3ffd390 --- /dev/null +++ b/lib/services/rational_team_concert.rb @@ -0,0 +1,163 @@ +class Service::RationalTeamConcert < Service + string :server_url, :username, :project_area_uuid + password :password + boolean :basic_authentication + boolean :no_verify_ssl + white_list :server_url, :username, :basic_authentication + attr_accessor :cookies + + def receive_push + checkSettings + prepare + authenticate + commit_changes + end + + def server_url + #trim trailing / if provided + @server_url ||= data['server_url'].gsub(/\/$/, '') + end + + def checkSettings + raise_config_error "Server Url url not set" if data['server_url'].blank? + raise_config_error "username not set" if data['username'].blank? + raise_config_error "password not set" if data['password'].blank? + raise_config_error "Project Area UUID is not set" if data['project_area_uuid'].blank? + end + + def prepare + if config_boolean_true?('no_verify_ssl') + http.ssl[:verify] = false + end + http.headers['X-com-ibm-team-userid']= data['username'] + end + + def authenticate + if config_boolean_true?('basic_authentication') + http.basic_auth data['username'], data['password'] + else + form_based_authentification + end + end + + def form_based_authentification + res= http_get '%s/authenticated/identity' % server_url + if not 'authrequired'.eql? res.headers['X-com-ibm-team-repository-web-auth-msg'] + # Expect one follow for WAS login + if res.env[:status] == 302 + captureCookies res + http.headers['Cookie']= cookieString + res = http_get res.env[:response_headers][:location] + if not 'authrequired'.eql? res.headers['X-com-ibm-team-repository-web-auth-msg'] + raise_config_error "Invalid authentication url. The response did not include a X-com-ibm-team-repository-web-auth-msg header" + end + else + raise_config_error "Invalid authentication url. The response did not include a X-com-ibm-team-repository-web-auth-msg header" + end + end + http.headers['Cookie']= captureCookies res + http.headers['Content-Type']= 'application/x-www-form-urlencoded' + + res= http_post '%s/authenticated/j_security_check' % server_url, + Faraday::Utils.build_nested_query(http.params.merge(:j_username => data['username'], :j_password => data['password'])) + + if 'authrequired'.eql? res.headers['X-com-ibm-team-repository-web-auth-msg'] + raise_config_error 'Invalid Username or Password' + end + + http.headers['Cookie']= captureCookies res + http.headers['Content-Type']= '' + res= http_get '%s/authenticated/identity' % server_url + captureCookies res + end + + def captureCookies (response) + @cookies ||= Hash.new + setstring = response.headers['set-cookie'] + if setstring + setstring.split(/, (?=[\w]+=)/).each { | cookie | + # trim off the cookie domain and update info + cookiepair = cookie.split('; ')[0]; + # split the key and value + cookieparts = cookiepair.split('=') + cookies[cookieparts[0]] = cookiepair[cookieparts[0].size+1..-1] + } + end + return cookieString + end + + def cookieString + result = '' + if cookies + cookies.each { |key, value| + result += key + '=' + value + (cookies.size > 1 ? ";" : "") + } + end + return result + end + + def commit_changes + http.headers['Content-Type']= 'application/json' + http.headers['oslc-core-version']= '2.0' + http.headers['accept']= 'application/json' + + payload['commits'].each do |commit| + next if commit['message'] =~ /^x / + commit['message'].match(/\[(#?(\d+)|[a-zA-Z0-9]+)[^\]]*\]/) + next unless $1 + + comment_body= generate_comment_body commit + work_item= $2 ? get_work_item($2) : new_work_item(commit, $1) + post_comment work_item, comment_body + end + end + + def generate_comment_body (commit) + + comment_body= "[Message] #{commit['message']}
" + + "[Author] #{commit['author']['name']}(#{commit['author']['email']})
" + + "[Url] #{commit['url']}
" + + comment_body= comment_body + "[Modified]
#{commit['modified'].join('
')}
" if commit['modified'].length > 0 + comment_body= comment_body + "[Added]
#{commit['added'].join('
')}
" if commit['added'].length > 0 + comment_body= comment_body + "[Removed]
#{commit['removed'].join('
')}
" if commit['removed'].length > 0 + return comment_body + end + + def get_work_item (work_item_number) + http.headers['Cookie']= cookieString + res= http_get "%s/resource/itemName/com.ibm.team.workitem.WorkItem/%s?oslc.properties=oslc:discussedBy" % [ server_url, work_item_number ] + # Expect one follow for WAS login + if res.env[:status] == 302 + captureCookies res + http.headers['Cookie']= cookieString + res = http_get res.env[:response_headers][:location] + end + return res.body + end + + def new_work_item (commit, work_item_type) + url= "%s/oslc/contexts/%s/workitems/%s" % [server_url, data['project_area_uuid'], work_item_type] + work_item= { 'dcterms:title' => commit['message']} + http.headers['Cookie']= cookieString + res= http_post url, generate_json(work_item) + raise_config_error "Work item was not created. Make sure that its possible to create work items with no additional required fields" unless res.status == 201 + return res.body + end + + def post_comment(work_item, comment_body) + comment_url= get_comment_url work_item + comment= { 'dcterms:description' => comment_body } + http.headers['Cookie']= cookieString + res= http_post comment_url, generate_json(comment) + raise_config_error "Not possible to add comments with the current setup" unless res.status == 201 + end + + def get_comment_url (work_item) + answer= JSON.parse(work_item) + raise_config_error "Invalid OSLC response. Unable to parse the work item" unless answer + discussedBy= answer['oslc:discussedBy'] + raise_config_error "Invalid OSLC response. Expected to receive oslc:discussedBy in the response" unless discussedBy + return "#{discussedBy['rdf:resource']}/oslc:comment" + end +end diff --git a/lib/services/rdocinfo.rb b/lib/services/rdocinfo.rb new file mode 100644 index 000000000..4489a3d36 --- /dev/null +++ b/lib/services/rdocinfo.rb @@ -0,0 +1,15 @@ +class Service::RDocInfo < Service::HttpPost + + default_events :push + + title 'RubyDoc.info' + url 'http://www.rubydoc.info' + + maintained_by :github => 'zapnap' + + supported_by :web => 'http://www.rubydoc.info', :github => 'zapnap' + + def receive_event + deliver 'http://www.rubydoc.info/checkout' + end +end diff --git a/lib/services/read_the_docs.rb b/lib/services/read_the_docs.rb new file mode 100644 index 000000000..50679a24d --- /dev/null +++ b/lib/services/read_the_docs.rb @@ -0,0 +1,6 @@ +class Service::ReadTheDocs < Service + def receive_push + http_post "https://readthedocs.org/github", :payload => generate_json(payload) + end +end + diff --git a/lib/services/redmine.rb b/lib/services/redmine.rb new file mode 100644 index 000000000..b64eb49a7 --- /dev/null +++ b/lib/services/redmine.rb @@ -0,0 +1,99 @@ +class Service::Redmine < Service + string :address, :project, :api_key + boolean :fetch_commits + boolean :update_redmine_issues_about_commits + white_list :address, :project + + def receive_push + + if fetch_github_commits_enabled? + http.ssl[:verify] = false + http.url_prefix = data['address'] + http_get "sys/fetch_changesets" do |req| + req.params['key'] = data['api_key'] + req.params['id'] = data['project'] + end + end + + if update_issues_enabled? + begin + # check configurations first + check_configuration_options(data) + + payload['commits'].each do |commit| + message = commit['message'].clone + + #Extract issue IDs and send update to the related issues + while !(id= message[/#(\d)+/]).nil? do + message.gsub!(id,'') + issue_no = id.gsub('#','') + + # Send the commit information to the related issue on redmine + http.url_prefix = data['address'] + + http_method :put, "issues/#{issue_no}.json" do |req| + req.headers['Content-Type'] = 'application/json' + req.headers['X-Redmine-API-Key'] = data['api_key'] + req.params['issue[notes]'] = commit_text(commit) + end + end + end + return true + rescue SocketError => se + puts "SocketError has occurred: #{se.inspect}" + return false + rescue Exception => e + puts "Other Exception has occurred: #{e.inspect}" + return false + end + end + end + + private + def check_configuration_options(data) + raise_config_error 'Redmine url must be set' if data['address'].blank? + raise_config_error 'API key is required' if data['api_key'].blank? + end + + def fetch_github_commits_enabled? + config_boolean_true?('fetch_commits') + end + + def update_issues_enabled? + config_boolean_true?('update_redmine_issues_about_commits') + end + + #Extract and buffer the needed commit information into one string + def commit_text(commit) + gitsha = commit['id'] + added = commit['added'].map { |f| ['A', f] } + removed = commit['removed'].map { |f| ['R', f] } + modified = commit['modified'].map { |f| ['M', f] } + + timestamp = Date.parse(commit['timestamp']) + + commit_author = "#{commit['author']['name']} <#{commit['author']['email']}>" + + text = align(<<-EOH) + Commit: #{gitsha} + #{commit['url']} + Author: #{commit_author} + Date: #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')}) + + EOH + + text << align(<<-EOH) + Log Message: + ----------- + #{commit['message']} + EOH + + text + end + + def align(text, indent = ' ') + margin = text[/\A\s+/].size + text.gsub(/^\s{#{margin}}/, indent) + end + +end diff --git a/services/scrumdo.rb b/lib/services/scrumdo.rb similarity index 62% rename from services/scrumdo.rb rename to lib/services/scrumdo.rb index 14f8131e0..94f3aea17 100644 --- a/services/scrumdo.rb +++ b/lib/services/scrumdo.rb @@ -1,7 +1,7 @@ class Service::ScrumDo < Service - string :username - string :password - string :project_slug + string :username, :project_slug + password :password + white_list :project_slug, :username def receive_push username = data["username"] @@ -9,13 +9,12 @@ def receive_push url = "http://www.scrumdo.com/hooks/github/#{data["project_slug"]}" res = http_post url do |req| - req.body = {:payload => payload.to_json, :username=>username, :password=>password} + req.body = {:payload => generate_json(payload), :username=>username, :password=>password} end if res.status < 200 || res.status > 299 raise_config_error end - end end diff --git a/lib/services/sifter.rb b/lib/services/sifter.rb new file mode 100644 index 000000000..ec432dcaf --- /dev/null +++ b/lib/services/sifter.rb @@ -0,0 +1,33 @@ +class Service::Sifter < Service + string :subdomain + password :token + + def receive_push + http.ssl[:verify] = false + + http_post hook_url do |req| + req.params[:token] = token + req.headers['Content-Type'] = 'application/json' + req.body = generate_json(payload) + end + end + + def hook_url + # For development/troubleshooting, the host and protocol can be set + # with the SIFTER_HOST variable, e.g. SIFTER_HOST=http://sifter.dev + host = ENV.fetch('SIFTER_HOST', 'sifterapp.com') + proto = ENV.has_key?('SIFTER_HOST') ? 'http' : 'https' + + "#{proto}://#{subdomain}.#{host}/api/github" + end + + def subdomain + data["subdomain"].to_s.strip + end + + def token + data["token"].to_s.strip + end + +end + diff --git a/lib/services/simperium.rb b/lib/services/simperium.rb new file mode 100644 index 000000000..cde1a1f58 --- /dev/null +++ b/lib/services/simperium.rb @@ -0,0 +1,43 @@ +class Service::Simperium < Service::HttpPost + string :app_id, :bucket + password :token + + white_list :app_id, :bucket + + default_events :push, :issues, :issue_comment, :commit_comment, + :pull_request, :pull_request_review_comment, :watch, :fork, + :fork_apply, :member, :public, :team_add, :status + + url "https://simperium.com" + logo_url "https://simperium.com/media/images/simperium_logo_black_sm.png" + + maintained_by :github => 'fredrocious', + :twitter => '@fredrocious' + + supported_by :web => 'https://simperium.com/contact', + :email => 'help@simperium.com', + :twitter => '@simperium' + + def receive_event + appid = required_config_value('app_id') + token = required_config_value('token') + bucket = required_config_value('bucket') + + if appid.match(/^[A-Za-z0-9-]+$/) == nil + raise_config_error "Invalid app id" + end + + if token.match(/^[A-Za-z0-9]+$/) == nil + raise_config_error "Invalid token" + end + + if bucket.match(/^[A-Za-z0-9\-\.@]+$/) == nil + raise_config_error "Invalid bucket name" + end + + http.headers['Authorization'] = "Token #{token}" + + url = "https://api.simperium.com:443/1/#{appid}/#{bucket}/i/#{delivery_guid}" + deliver url + end +end diff --git a/lib/services/skydeskprojects.rb b/lib/services/skydeskprojects.rb new file mode 100644 index 000000000..538ecbb1a --- /dev/null +++ b/lib/services/skydeskprojects.rb @@ -0,0 +1,31 @@ +# encoding: utf-8 +class Service::SkyDeskProjects < Service::HttpPost + string :project_id + password :token + + white_list :project_id + + url "https://www.skydesk.jp" + logo_url "https://www.skydesk.jp/static/common/images/header/ci/ci_skydesk.gif" + + maintained_by :github => 'SkyDeskProjects' + + supported_by :web => 'www.skydesk.jp/en/contact/', + :email => 'support_projects@skydesk.jp' + + def receive_push + token = required_config_value('token') + pId = required_config_value('project_id') + #http.headers['Authorization'] = "Token #{token}" + + #url = "https://projects.skydesk.jp/serviceHook" + res = http_post "https://projects.skydesk.jp/serviceHook", + :pId => pId, + :authtoken => token, + :scope => "projectsapi", + :payload => generate_json(payload) + if res.status != 200 + raise_config_error + end + end +end diff --git a/lib/services/smartling.rb b/lib/services/smartling.rb new file mode 100644 index 000000000..93f1eecf4 --- /dev/null +++ b/lib/services/smartling.rb @@ -0,0 +1,39 @@ +class Service::Smartling < Service + + url "http://smartling.com" + + maintained_by :github => 'smartling' + + supported_by :web => 'http://support.smartling.com', + :email => 'support@smartling.com' + + string :service_url, :project_id + password :api_key + string :config_path + boolean :master_only + white_list :service_url, :project_id, :config_path + + def receive_push + check_config + if config_boolean_false?("master_only") || payload["ref"] == "refs/heads/master" + payload["projectId"] = data["project_id"] + payload["apiKey"] = data["api_key"] + payload["resourceFile"] = data["config_path"] + + http.url_prefix = data["service_url"].to_s + res = http_post "github", generate_json(payload) + + if res.status < 200 || res.status > 299 + raise_config_error "Status: " + res.status.to_s + ", body: " + res.body + end + end + end + + def check_config + raise_config_error "Missing smartling broker url" if data["service_url"].to_s.empty? + raise_config_error "Missing project id" if data["project_id"].to_s.empty? + raise_config_error "Missing smartling api key" if data["api_key"].to_s.empty? + raise_config_error "Missing path to the project configuration" if data["config_path"].to_s.empty? + end + +end diff --git a/lib/services/snowyevening.rb b/lib/services/snowyevening.rb new file mode 100644 index 000000000..6a844562d --- /dev/null +++ b/lib/services/snowyevening.rb @@ -0,0 +1,10 @@ +class Service::SnowyEvening < Service + string :project, :api_key + + def receive_push + http.ssl[:verify] = false + res = http_post "https://snowy-evening.com/api/integration/github_commit/"+data['api_key']+"/"+data['project'], + :payload => generate_json(payload) + return + end +end diff --git a/lib/services/softlayer_messaging.rb b/lib/services/softlayer_messaging.rb new file mode 100644 index 000000000..14774b1e7 --- /dev/null +++ b/lib/services/softlayer_messaging.rb @@ -0,0 +1,104 @@ +class Service::SoftLayerMessaging < Service + string :account, :user, :name + password :key + boolean :topic + white_list :account, :user, :name + + attr_writer :client + + # receive_push + def receive_push + return unless data && payload + + if data['account'].to_s.empty? + raise_config_error "You must define an publishing account." + end + + if data['user'].to_s.empty? + raise_config_error "You must define an authorized user." + end + + if data['name'].to_s.empty? + raise_config_error "You must define a queue/topic name." + end + + if data['key'].to_s.empty? + raise_config_error "You must provide the api key." + end + + publish_message(account, user, apikey, name, data['topic'], payload) + + end + + # publish_message + def publish_message(account, user, key, name, topic, payload) + client.authenticate(user, key) + # mungle + options = { + :fields => { + :repository => payload['repository']['name'], + :owner => payload['repository']['owner']['name'], + :email => payload['repository']['owner']['email'], + :ref => payload['ref'], + :created => payload['created'], + :forced => payload['forced'], + :deleted => payload['deleted'] + } + } + + # Encode payload to JSON + # hopefully not bigger than 64K + payload_json_data = generate_json(payload) + + begin + if topic + push_to_topic(name, payload_json_data, options) + else + push_to_queue(name, payload_json_data, options) + end + + rescue RestClient::ServerBrokeConnection => broken + raise_config_error "Disconnected #{broken.to_s}" + rescue RestClient::ExceptionWithResponse => req + case req.http_code + when 404 + raise_config_error 'Topic or Queue not created prior to publishing' + when 401 + raise_config_error 'Bad SoftLayer messaging authentication information' + else + raise_config_error "Unhandled SoftLayer response: #{req}" + end + end + end + + def push_to_topic(name, payload, options={}) + topic = client.topic(name) + topic.publish(payload, options) + end + + def push_to_queue(name, payload, options={}) + queue = client.queue(name) + queue.push(payload, options) + end + + def client + @client ||= SL::Messaging::Client.new(data['account']) + end + + def account + data['account'].strip + end + + def apikey + data['key'].strip + end + + def user + data['user'].strip + end + + def name + data['name'].strip + end + +end diff --git a/lib/services/sprintly.rb b/lib/services/sprintly.rb new file mode 100644 index 000000000..abfd0db4f --- /dev/null +++ b/lib/services/sprintly.rb @@ -0,0 +1,21 @@ +class Service::Sprintly < Service + default_events :commit_comment, :create, :delete, :download, + :follow, :fork, :fork_apply, :gist, :gollum, :issue_comment, + :issues, :member, :public, :pull_request, :push, :team_add, + :watch, :pull_request_review_comment, :status + string :email, :api_key, :product_id + white_list :email, :product_id + + def receive_event + raise_config_error "Must provide an api key" if data['api_key'].to_s.empty? + raise_config_error "Must provide an email address." if data['email'].to_s.empty? + raise_config_error "Must provide a product id." if data['product_id'].to_s.empty? + host_name = ENV['SPRINTLY_DEBUG_HOST'] || "https://sprint.ly" + + http.headers['Content-Type'] = 'application/json' + http.basic_auth(data['email'], data['api_key']) + + http_post "#{host_name}/integration/github/#{data['product_id']}/#{event}/", generate_json(payload) + end +end + diff --git a/lib/services/sqs_queue.rb b/lib/services/sqs_queue.rb new file mode 100644 index 000000000..7f4daa70f --- /dev/null +++ b/lib/services/sqs_queue.rb @@ -0,0 +1,100 @@ +class Service::SqsQueue < Service::HttpPost + self.title = "Amazon SQS" + + string :aws_access_key, :aws_sqs_arn + password :aws_secret_key + # NOTE: at some point, sqs_queue_name needs to be deprecated and removed + white_list :aws_access_key, :sqs_queue_name, :aws_sqs_arn + + maintained_by github: 'brycem', + twitter: 'brycemcd' + + def receive_event + return unless data && payload + + if data['aws_access_key'].to_s.empty? + raise_config_error "You must define an AWS access key." + end + + if data['aws_secret_key'].to_s.empty? + raise_config_error "You must define an AWS secret key." + end + + if data['sqs_queue_name'].to_s.empty? && data['aws_sqs_arn'].to_s.empty? + raise_config_error "You must define an SQS queue name or SQS queue ARN." + end + + # Encode payload to JSON + payload_json_data = generate_json(payload) + + # Send payload to SQS queue + notify_sqs( access_key, secret_key, payload_json_data ) + end + + def notify_sqs(aws_access_key, aws_secret_key, payload) + sqs = sqs_client + + if data['aws_sqs_arn'] && data['aws_sqs_arn'].match(/^http/) + queue = sqs.queues[data['aws_sqs_arn']] + else + queue = sqs.queues.named(queue_name) + end + sqs.client.send_message( + queue_url: queue.url, + message_body: clean_for_json(payload), + message_attributes: { + 'X-GitHub-Event' => { string_value: event.to_s, data_type: 'String'} + } + ) + end + + def access_key + data['aws_access_key'].strip + end + + def secret_key + data['aws_secret_key'].strip + end + + def queue_name + arn[:queue_name] || data['sqs_queue_name'].strip + end + + def region + arn[:region] || 'us-east-1' + end + + def stubbed_requests? + !!ENV['SQS_STUB_REQUESTS'] + end + + def aws_config + { + :region => region, + :logger => stubbed_requests? ? nil : Logger.new(STDOUT), + :access_key_id => access_key, + :secret_access_key => secret_key, + :stub_requests => stubbed_requests?, + } + end + + def sqs_client + @sqs_client ||= ::AWS::SQS.new(aws_config) + end + + private + + def arn + @arn ||= parse_arn + end + + def parse_arn + return {} unless data['aws_sqs_arn'] && !data['aws_sqs_arn'].match(/^http/) + _,_,service,region,id,queue_name = data['aws_sqs_arn'].split(":") + {service: service.strip, + region: region.strip, + id: id.strip, + queue_name: queue_name.strip + } + end +end diff --git a/services/statusnet.rb b/lib/services/statusnet.rb similarity index 93% rename from services/statusnet.rb rename to lib/services/statusnet.rb index b4f6a86ba..2c965c12a 100644 --- a/services/statusnet.rb +++ b/lib/services/statusnet.rb @@ -2,12 +2,13 @@ class Service::StatusNet < Service string :server, :username password :password boolean :digest + white_list :server, :username def receive_push repository = payload['repository']['name'] statuses = Array.new - if data['digest'] == '1' + if config_boolean_true?('digest') commit = payload['commits'][-1] tiny_url = shorten_url("#{payload['repository']['url']}/commits/#{ref_name}") statuses.push "[#{repository}] #{tiny_url} #{commit['author']['name']} - #{payload['commits'].length} commits" diff --git a/services/talker.rb b/lib/services/talker.rb similarity index 86% rename from services/talker.rb rename to lib/services/talker.rb index 47bf37f0e..c52f47263 100644 --- a/services/talker.rb +++ b/lib/services/talker.rb @@ -1,6 +1,9 @@ +# coding: utf-8 class Service::Talker < Service - string :url, :token + string :url + password :token boolean :digest + white_list :url def receive_push repository = payload['repository']['name'] @@ -10,7 +13,7 @@ def receive_push prepare_http say "#{summary_message} – #{summary_url}" - if data['digest'].to_i == 0 + if config_boolean_false?('digest') if distinct_commits.size == 1 commit = distinct_commits.first say format_commit_message(commit) @@ -26,7 +29,7 @@ def receive_pull_request return unless opened? prepare_http - say summary_message + say "#{summary_message}. #{summary_url}" end def receive_issues diff --git a/services/target_process.rb b/lib/services/target_process.rb similarity index 51% rename from services/target_process.rb rename to lib/services/target_process.rb index 2563405f3..34b12a5c1 100644 --- a/services/target_process.rb +++ b/lib/services/target_process.rb @@ -1,11 +1,13 @@ class Service::TargetProcess < Service - string :base_url, :username, :project_id + string :base_url, :username password :password + white_list :base_url, :username def receive_push # setup things for our REST calls http.ssl[:verify] = false http.url_prefix = data['base_url'] + http.headers['Content-Type'] = 'application/json' http.basic_auth(data['username'], data['password']) @project_id = data['project_id'] # And go! @@ -13,7 +15,7 @@ def receive_push |commit| process_commit(commit) } end - + private def valid_response?(res) case res.status @@ -29,48 +31,32 @@ def process_commit(commit) author = commit["author"]["email"] commit_url = commit["url"] commit["message"].split("\n").each { |commit_line| - parts = commit_line.match(/(\s|^)#(\d+)(:[^\s]+)?(\s|$)/) + parts = commit_line.match(/(\s|^)(?#|id:)(\d+)(:[^\s]+)?(\s|$)/) next if parts.nil? - entity_id = parts[2].strip + entity_id = parts[3].strip next if entity_id.nil? or entity_id.length == 0 - if parts[3].nil? or parts[3].length == 0 + if parts[4].nil? or parts[4].length == 0 command = nil else - command = parts[3].strip + command = parts[4].strip end - execute_command(author, entity_id, command, commit_line, commit_url) + execute_command(author, entity_id, command, commit_line, commit_url, commit) } end - def execute_command(author, entity_id, command, commit_message, commit_url) + def execute_command(author, entity_id, new_state, commit_message, commit_url, raw_commit) return if command.nil? + require 'json' # get the user's id res = http_get "api/v1/Users", {:where => "(Email eq '%s')" % author} valid_response?(res) - author_id = begin Hash.from_xml(res.body)['Items']['User']['Id'] rescue nil end + author_id = begin JSON.parse(res.body)['Users']['User']['Id'] rescue nil end return if author_id.nil? - # get Context data for our project - res = http_get "api/v1/Context", :ids => @project_id - valid_response?(res) - context_data = Hash.from_xml res.body - acid = context_data['Context']['Acid'] - # get the assignable's type - res = http_get "api/v1/Assignables/%s" % entity_id, {:include => '[EntityType]', :acid => acid} - valid_response?(res) - assignable = Hash.from_xml res.body - return if assignable.nil? - entity_type = assignable['Assignable']['EntityType']['Name'] - # Gather next state's ID - res = http_get "api/v1/Processes/%s/EntityStates" % [context_data['Context']['Processes']['ProcessInfo']['Id']], - {:where => "(Name eq '%s') and (EntityType.Name eq '%s')" % [command, entity_type], :acid => acid} - valid_response?(res) - new_state = begin Hash.from_xml(res.body)['Items']['EntityState']['Id'] rescue nil end # Make it happen - http.headers['Content-Type'] = 'application/json' commit_message = "#{commit_message}
Commit: #{commit_url}" valid_response?(http_post "api/v1/Comments", "{General: {Id: #{entity_id}}, Description: '#{commit_message.gsub("'","\'")}', Owner: {Id: #{author_id}}}") - if !command.nil? and !new_state.nil? - valid_response?(http_post "api/v1/%s" % ((entity_type == "UserStory") ? "UserStories" : entity_type+'s'), "{Id: #{entity_id}, EntityState: {Id: #{new_state}}}") + if !new_state.nil? + valid_response?(http_post "api/v1/Assignables/%s" % entity_id, "{EntityState: {Name: #{new_state}}}") end end end diff --git a/lib/services/tddium.rb b/lib/services/tddium.rb new file mode 100644 index 000000000..6adbecafa --- /dev/null +++ b/lib/services/tddium.rb @@ -0,0 +1,23 @@ +class Service::Tddium < Service::HttpPost + password :token + string :override_url + + white_list :override_url + + url "https://www.tddium.com" + logo_url "https://www.tddium.com/favicon.ico" + + maintained_by :github => 'solanolabs', :twitter => '@solanolabs' + supported_by :web => 'https://support.tddium.com/', :email => 'support@tddium.com' + + default_events Service::ALL_EVENTS + + def receive_event + token = required_config_value('token') + override_url = data['override_url'] + + url_base = override_url.present? ? override_url : "https://hooks.tddium.com:443/1/github" + tddium_url = "#{url_base}/#{token}" + deliver tddium_url + end +end diff --git a/lib/services/teamcity.rb b/lib/services/teamcity.rb new file mode 100644 index 000000000..cd16293cf --- /dev/null +++ b/lib/services/teamcity.rb @@ -0,0 +1,75 @@ +class Service::TeamCity < Service + string :base_url, :build_type_id, :branches + boolean :full_branch_ref + string :username + password :password + boolean :check_for_changes_only + white_list :base_url, :build_type_id, :branches, :username, :full_branch_ref + + maintained_by :github => 'JetBrains' + + supported_by :web => 'http://confluence.jetbrains.com/display/TW/Feedback', + :email => 'teamcity-support@jetbrains.com' + + def receive_push + return if payload['deleted'] + + check_for_changes_only = config_boolean_true?('check_for_changes_only') + + branches = data['branches'].to_s.split(/\s+/) + ref = payload["ref"].to_s + branch = config_boolean_true?('full_branch_ref') ? ref : ref.split("/", 3).last + return unless branches.empty? || branches.include?(branch) + + # :( + http.ssl[:verify] = false + + base_url = data['base_url'].to_s + if base_url.empty? + raise_config_error "No base url: #{base_url.inspect}" + end + + http.headers['Content-Type'] = 'application/xml' + http.url_prefix = base_url + http.basic_auth data['username'].to_s, data['password'].to_s + build_type_ids = data['build_type_id'].to_s + build_type_ids.split(",").each do |build_type_id| + + res = perform_post_request(build_type_id, check_for_changes_only, branch: branch) + + # Hotfix for older TeamCity versions (older than 2017.1.1) where a GET is needed + if res.status == 415 || res.status == 405 + res = perform_get_request(build_type_id, check_for_changes_only, branch: branch) + end + + case res.status + when 200..299 + when 403, 401, 422 then raise_config_error("Invalid credentials") + when 404, 301, 302 then raise_config_error("Invalid TeamCity URL") + else raise_config_error("HTTP: #{res.status}") + end + + end + rescue SocketError => e + raise_config_error "Invalid TeamCity host name" if e.to_s =~ /getaddrinfo: Name or service not known/ + raise + end + + # This is undocumented call. TODO: migrate to REST API (TC at least 8.0) + def perform_post_request(build_type_id, check_for_changes_only, branch: nil) + if check_for_changes_only + http_post "httpAuth/app/rest/vcs-root-instances/checkingForChangesQueue?locator=buildType:#{build_type_id}" + else + http_post "httpAuth/app/rest/buildQueue", "" + end + end + + # This is undocumented call. TODO: migrate to REST API (TC at least 8.0) + def perform_get_request(build_type_id, check_for_changes_only, branch: nil) + if check_for_changes_only + http_get "httpAuth/action.html", :checkForChangesBuildType => build_type_id + else + http_get "httpAuth/action.html", :add2Queue => build_type_id, :branchName => branch + end + end +end diff --git a/lib/services/tender.rb b/lib/services/tender.rb new file mode 100644 index 000000000..52d8c9a28 --- /dev/null +++ b/lib/services/tender.rb @@ -0,0 +1,48 @@ +class Service::Tender < Service + # mysite.tenderapp.com + string :domain + + # tracker token. can be found here: + # http://mysite.tenderapp.com/settings/trackers + password :token + + default_events :issues, :pull_request + + url 'https://tenderapp.com' + logo_url 'https://tenderapp.com/images/logo.jpg' + + # julien on http://help.tenderapp.com/home + maintained_by :github => 'calexicoz' + + # Support channels for user-level Hook problems (service failure, misconfigured) + supported_by :web => 'http://help.tenderapp.com/home', + :email => 'support@tenderapp.com' + + def receive_issues + raise_config_error 'Missing token' if data['token'].to_s.empty? + raise_config_error 'Missing domain' if data['domain'].to_s.empty? + + begin + # Nothing to see here really, just reposting the payload as-is + http.headers['content-type'] = 'application/json' + http.ssl[:verify] = false + body = generate_json(payload) + url = "https://#{data['domain']}/tickets/github/#{data['token']}" + http_post url, body + + # Shamelessly copied from the 'web' service + rescue Addressable::URI::InvalidURIError, Errno::EHOSTUNREACH + raise_missing_error $!.to_s + rescue SocketError + if $!.to_s =~ /getaddrinfo:/ + raise_missing_error "Invalid host name." + else + raise + end + rescue EOFError + raise_config_error "Invalid server response. Make sure the URL uses the correct protocol." + end + end + + alias receive_pull_request receive_issues +end diff --git a/services/toggl.rb b/lib/services/toggl.rb similarity index 67% rename from services/toggl.rb rename to lib/services/toggl.rb index 72a5c3dad..97925a422 100644 --- a/services/toggl.rb +++ b/lib/services/toggl.rb @@ -1,8 +1,10 @@ class Service::Toggl < Service - string :project, :api_token + string :project + password :api_token + white_list :project def receive_push - http.url_prefix = "https://www.toggl.com/api/v5" + http.url_prefix = "https://www.toggl.com/api/v8" http.basic_auth data['api_token'], 'api_token' http.headers['Content-Type'] = 'application/json' @@ -12,19 +14,17 @@ def receive_push # Toggl wants it in seconds. Commits should be in seconds duration = duration.to_i * 60 - - http_post "tasks.json", { - :task => { + http_post "time_entries", generate_json( + :time_entry => { :duration => duration.to_i, :description => commit["message"].strip, - :project => data["project"], + :pid => data["project"], :start => (Time.now - duration.to_i).iso8601, - :billable => true, + :billable => true, # this is a pro feature, will be ignored for free version users :created_with => "github", :stop => Time.now.iso8601 } - }.to_json - + ) end end end diff --git a/services/trac.rb b/lib/services/trac.rb similarity index 64% rename from services/trac.rb rename to lib/services/trac.rb index a1d8a3428..c60b1b79d 100644 --- a/services/trac.rb +++ b/lib/services/trac.rb @@ -1,10 +1,12 @@ class Service::Trac < Service - string :url, :token + string :url + password :token + white_list :url def receive_push http.ssl[:verify] = false http.url_prefix = data['url'] - http_post "github/#{data['token']}", :payload => payload.to_json + http_post "github/#{data['token']}", :payload => generate_json(payload) rescue Faraday::Error::ConnectionFailed raise_config_error "Connection refused. Invalid server URL." end diff --git a/lib/services/travis.rb b/lib/services/travis.rb new file mode 100644 index 000000000..b9821d16f --- /dev/null +++ b/lib/services/travis.rb @@ -0,0 +1,65 @@ +class Service::Travis < Service + self.title = "Travis CI" + url "https://travis-ci.org" + + maintained_by :github => 'travisci' + + supported_by :email => 'support@travis-ci.com' + + default_events :push, :pull_request, :issue_comment, :public, :member + string :user + password :token + string :domain + white_list :domain, :user + + def receive_event + http.ssl[:verify] = false + http.basic_auth user, token + http.headers['X-GitHub-Event'] = event.to_s + http.headers['X-GitHub-GUID'] = delivery_guid.to_s + http_post travis_url, :payload => generate_json(payload) + end + + def travis_url + "#{scheme}://#{domain}" + end + + def user + if data['user'].to_s == '' + owner_payload['login'] || owner_payload['name'] + else + data['user'] + end.strip + end + + def token + data['token'].to_s.strip + end + + def scheme + domain_parts.size == 1 ? 'http' : domain_parts.first + end + + def domain + domain_parts.last + end + + protected + + def owner_payload + payload['repository']['owner'] + end + + def full_domain + if data['domain'].present? + data['domain'] + else + 'http://notify.travis-ci.org' + end.strip + end + + def domain_parts + @domain_parts ||= full_domain.split('://') + end +end + diff --git a/lib/services/trello.rb b/lib/services/trello.rb new file mode 100644 index 000000000..86d277303 --- /dev/null +++ b/lib/services/trello.rb @@ -0,0 +1,150 @@ +class Service::Trello < Service + string :push_list_id, :pull_request_list_id, :ignore_regex + boolean :master_only + password :consumer_token + + default_events :push, :pull_request + + def receive_pull_request + return unless opened? + assert_required_credentials :pull_request + create_card :pull_request, name_for_pull(pull), desc_for_pull(pull) + end + + def receive_push + return unless process_commits? + assert_required_credentials :push + process_commits :push + end + + private + + def name_for_pull(pull) + pull.title + end + + def desc_for_pull(pull) + "Author: %s\n\n%s\n\nDescription: %s" % [ + pull.user.login, + pull.html_url, + pull.body || '[no description]' + ] + end + + def http_post(*args) + http.url_prefix = "https://api.trello.com/1" + super + end + + def process_commits? + payload['commits'].size > 0 && (config_boolean_false?('master_only') || branch_name == 'master') + end + + def assert_required_credentials(event) + if consumer_token.empty? + raise_config_error "You need an authorization Token. See tips below." + end + if list_id(event).empty? + raise_config_error "You need to enter a list identifiter. See tips below." + end + end + + def create_card(event, name, description) + http_post "cards", + :name => name, + :desc => description, + :idList => list_id(event), + :key => application_key, + :token => consumer_token + end + + def process_commits(event) + payload['commits'].each do |commit| + next if ignore_commit? commit + create_card event, name_for_commit(commit), desc_for_commit(commit) + find_card_ids(commit['message'] || '').each do |card_id| + comment_on_card card_id, card_comment(commit) + end + end + end + + def card_comment(commit) + "#{commit_author(commit)} added commit #{commit['url']}" + end + + def comment_on_card(card_id, message) + http_post "cards/#{card_id}/actions/comments", + :text => message, + :key => application_key, + :token => consumer_token + end + + def find_card_ids(message) + message.scan(%r{https://trello.com/c/([a-z0-9]+)}i).flatten + end + + def ignore_commit? commit + Service::Timeout.timeout(0.500, TimeoutError) do + ignore_regex && ignore_regex.match(commit['message']) + end + end + + def truncate_message(message) + message.length > message_max_length ? message[0...message_max_length] + "..." : message + end + + def name_for_commit(commit) + truncate_message commit['message'] + end + + def commit_author(commit) + author = commit['author'] || {} + author['name'] || '[unknown]' + end + + def desc_for_commit(commit) + + + "Author: %s\n\n%s\n\nRepo: %s\n\nBranch: %s\n\nCommit Message: %s" % [ + commit_author(commit), + commit['url'], + repository, + branch_name, + commit['message'] || '[no description]' + ] + end + + def consumer_token + data['consumer_token'].to_s + end + + def list_id(event) + list = data["#{event}_list_id"] + + # this should make the old `list_id`, which was implicitly for push, + # backwards-compatible + list ||= data["list_id"] if event == :push + + list.to_s + end + + def ignore_regex + @_memoized_ignore_regexp ||= if data['ignore_regex'].to_s.blank? + nil + else + Regexp.new(data['ignore_regex'].to_s) + end + end + + def application_key + "db1e35883bfe8f8da1725a0d7d032a9c" + end + + def repository + payload['repository']['name'] + end + + def message_max_length + 80 + end +end diff --git a/services/twilio.rb b/lib/services/twilio.rb similarity index 90% rename from services/twilio.rb rename to lib/services/twilio.rb index 852008bd7..d6f7747b6 100644 --- a/services/twilio.rb +++ b/lib/services/twilio.rb @@ -2,6 +2,7 @@ class Service::Twilio < Service string :account_sid, :from_phone, :to_phone boolean :master_only password :auth_token + white_list :account_sid, :from_phone, :to_phone def receive_push check_configuration_options(data) @@ -14,7 +15,7 @@ def receive_push def send_notification?(data) notify_user = true - if data['master_only'].to_i == 1 && branch_name != 'master' + if config_boolean_true?('master_only') && branch_name != 'master' notify_user = false end notify_user diff --git a/lib/services/twitter.rb b/lib/services/twitter.rb new file mode 100644 index 000000000..4b6c3d043 --- /dev/null +++ b/lib/services/twitter.rb @@ -0,0 +1,113 @@ +class Service::Twitter < Service + password :token, :secret + string :filter_branch + boolean :digest, :short_format + TWITTER_SHORT_URL_LENGTH_HTTPS = 23 + + white_list :filter_branch + + def receive_push + return unless payload['commits'] + + commit_branch = (payload['ref'] || '').split('/').last || '' + filter_branch = data['filter_branch'].to_s + + # If filtering by branch then don't make a post + if (filter_branch.length > 0) && (commit_branch.index(filter_branch) == nil) + return false + end + + statuses = [] + repository = payload['repository']['name'] + + if config_boolean_true?('digest') + commit = payload['commits'][-1] + author = commit['author'] || {} + url = "#{payload['repository']['url']}/commits/#{ref_name}" + status = "[#{repository}] #{url} #{author['name']} - #{payload['commits'].length} commits" + status = if short_format? + "#{url} - #{payload['commits'].length} commits" + else + "[#{repository}] #{url} #{author['name']} - #{payload['commits'].length} commits" + end + length = status.length - url.length + TWITTER_SHORT_URL_LENGTH_HTTPS # The URL is going to be shortened by twitter. It's length will be at most 23 chars (HTTPS). + # How many chars of the status can we actually use? + # We can use 140 chars, have to reserve 3 chars for the railing dots (-3) + # also 23 chars for the t.co-URL (-23) but can fit the whole URL into the tweet (+url.length) + usable_chars = 140 - 3 - TWITTER_SHORT_URL_LENGTH_HTTPS + url.length + length >= 140 ? statuses << status[0..(usable_chars-1)] + '...' : statuses << status + else + payload['commits'].each do |commit| + author = commit['author'] || {} + url = commit['url'] + message = commit['message'] + # Strip out leading @s so that github @ mentions don't become twitter @ mentions + # since there's zero reason to believe IDs on one side match IDs on the other + message.gsub!(/\B[@οΌ ][[:word:]]/) do |word| + "@\u200b#{word[1..word.length]}" + end + status = if short_format? + "#{url} #{message}" + else + "[#{repository}] #{url} #{author['name']} - #{message}" + end + # Twitter barfs on asterisks so replace them with a slightly different unicode one. + status.gsub!("*", "οΉ‘") + # The URL is going to be shortened by twitter. It's length will be at most 23 chars (HTTPS). + length = status.length - url.length + TWITTER_SHORT_URL_LENGTH_HTTPS + # How many chars of the status can we actually use? + # We can use 140 chars, have to reserve 3 chars for the railing dots (-3) + # also 23 chars for the t.co-URL (-23) but can fit the whole URL into the tweet (+url.length) + usable_chars = 140 - 3 - TWITTER_SHORT_URL_LENGTH_HTTPS + url.length + length >= 140 ? statuses << status[0..(usable_chars-1)] + '...' : statuses << status + end + end + + statuses.each do |status| + post(status) + end + end + + def post(status) + params = { 'status' => status } + + access_token = ::OAuth::AccessToken.new(consumer, data['token'], data['secret']) + res = consumer.request(:post, "/1.1/statuses/update.json", + access_token, { :scheme => :query_string }, params) + if res.code !~ /^2\d\d/ + raise_response_error(res) + end + end + + def raise_response_error(res) + error = "Received HTTP #{res.code}" + if msg = response_error_message(res) + error << ": " + error << msg + end + + raise_config_error(error) + end + + def response_error_message(res) + JSON.parse(res.body)['errors'].map { |error| error['message'] }.join('; ') + rescue + end + + def consumer_key + secrets['twitter']['key'] + end + + def consumer_secret + secrets['twitter']['secret'] + end + + def consumer + @consumer ||= ::OAuth::Consumer.new(consumer_key, consumer_secret, + {:site => "https://api.twitter.com"}) + end + + def short_format? + config_boolean_true?('short_format') + end +end diff --git a/lib/services/typetalk.rb b/lib/services/typetalk.rb new file mode 100644 index 000000000..7c54e068b --- /dev/null +++ b/lib/services/typetalk.rb @@ -0,0 +1,68 @@ +class Service::Typetalk < Service::HttpPost + string :client_id, :topic, :restrict_to_branch + password :client_secret + white_list :topic, :restrict_to_branch + + default_events :push, :pull_request + + url "http://typetalk.in" + logo_url "https://deeb7lj8m1sjw.cloudfront.net/1.3.5/assets/images/common/logo.png" + + def receive_push + check_config() + + branch = payload['ref'].split('/').last + branch_restriction = data['restrict_to_branch'].to_s + if branch_restriction.length > 0 && branch_restriction.index(branch) == nil + return + end + send_message(format_pushed_message(payload)) + end + + def receive_pull_request + check_config() + + send_message(format_pull_request_message(payload)) + end + + def check_config + raise_config_error "Missing 'client_id'" if data['client_id'].to_s == '' + raise_config_error "Missing 'client_secret'" if data['client_secret'].to_s == '' + raise_config_error "Missing 'topic'" if data['topic'].to_s == '' + end + + def send_message(message) + http.url_prefix = 'https://typetalk.in' + http.headers['X-GitHub-Event'] = event.to_s + + # get an access_token + res = http_post '/oauth2/access_token', + { :client_id => data['client_id'], + :client_secret => data['client_secret'], + :grant_type => 'client_credentials', + :scope => 'topic.post',} + + json = JSON.parse(res.body) + http.headers['Authorization'] = "Bearer #{json['access_token']}" + + topics = data['topic'].to_s.split(",") + topics.each do |topic| + params = { + :message => message + } + res = http_post "/api/v1/topics/#{topic}", params + if res.status < 200 || res.status > 299 + raise_config_error + end + end + end + + def format_pushed_message(payload) + branch = payload['ref'].split('/').last + return "#{payload['pusher']['name']} has pushed #{payload['commits'].size} commit(s) to #{branch} at #{payload['repository']['name']}\n#{payload['compare']}" + end + def format_pull_request_message(payload) + return "#{payload['sender']['login']} #{payload['action']} pull request \##{payload['pull_request']['number']}: #{payload['pull_request']['title']}\n#{payload['pull_request']['html_url']}" + end + +end diff --git a/services/unfuddle.rb b/lib/services/unfuddle.rb similarity index 90% rename from services/unfuddle.rb rename to lib/services/unfuddle.rb index 6915fa203..a0c27da3f 100644 --- a/services/unfuddle.rb +++ b/lib/services/unfuddle.rb @@ -1,14 +1,18 @@ class Service::Unfuddle < Service string :subdomain, :repo_id, :username password :password + boolean :httponly + white_list :subdomain, :repo_id, :username def receive_push u_repoid = data['repo_id'].to_i repository = payload['repository']['name'] branch = branch_name before = payload['before'] + # use https by default since most accounts support SSL + protocol = config_boolean_true?('httponly') ? 'http' : 'https' - http.url_prefix = "https://#{data['subdomain']}.unfuddle.com" + http.url_prefix = "#{protocol}://#{data['subdomain']}.unfuddle.com" http.basic_auth data['username'], data['password'] # grab people data for matching author-id diff --git a/lib/services/web.rb b/lib/services/web.rb new file mode 100644 index 000000000..a9cf483f9 --- /dev/null +++ b/lib/services/web.rb @@ -0,0 +1,44 @@ +class Service::Web < Service + include HttpHelper + + string :url, + # old hooks send form params ?payload=JSON(...) + # new hooks should set content_type == 'json' + :content_type + + # adds a X-Hub-Signature of the body content + # X-Hub-Signature: sha1=.... + password :secret + + white_list :url, :content_type + + boolean :insecure_ssl # :( + + def receive_event + http.headers['X-GitHub-Event'] = event.to_s + http.headers['X-GitHub-Delivery'] = delivery_guid.to_s + + res = deliver data['url'], + :content_type => data['content_type'], + :insecure_ssl => config_boolean_true?('insecure_ssl'), + :secret => data['secret'] + + if res.status < 200 || res.status > 299 + raise_config_error "Invalid HTTP Response: #{res.status}" + end + end + + def original_body + payload + end + + def default_encode_body + encode_body_as_form + end + + def encode_body_as_form + http.headers['content-type'] = 'application/x-www-form-urlencoded' + Faraday::Utils.build_nested_query( + http.params.merge(:payload => generate_json(original_body))) + end +end diff --git a/services/web_translate_it.rb b/lib/services/web_translate_it.rb similarity index 81% rename from services/web_translate_it.rb rename to lib/services/web_translate_it.rb index f9aa0b04e..008282f57 100644 --- a/services/web_translate_it.rb +++ b/lib/services/web_translate_it.rb @@ -3,6 +3,6 @@ class Service::WebTranslateIt < Service def receive_push http_post "https://webtranslateit.com/api/projects/#{data['api_key']}/refresh_files", - :payload => JSON.generate(payload) + :payload => generate_json(payload) end end diff --git a/lib/services/weblate.rb b/lib/services/weblate.rb new file mode 100644 index 000000000..522923685 --- /dev/null +++ b/lib/services/weblate.rb @@ -0,0 +1,32 @@ +class Service::Weblate < Service + string :url + white_list :url + + url "http://weblate.org/" + logo_url "http://weblate.org/graphics/weblate-32.png" + maintained_by :github => 'nijel' + + def receive_push + url = data['url'] + url.gsub! /\s/, '' + + if url.empty? + raise_config_error "Invalid URL: #{url.inspect}" + end + + if url !~ /^https?\:\/\// + url = "http://#{url}" + end + + res = http_post "#{url}/hooks/github/", + :payload => generate_json(payload) + + if res.status < 200 || res.status > 299 + raise_config_error "Failed with #{res.status}" + end + rescue URI::InvalidURIError + raise_config_error "Invalid URL: #{data['url']}" + end +end + + diff --git a/lib/services/windowsazure.rb b/lib/services/windowsazure.rb new file mode 100644 index 000000000..13c08fcc1 --- /dev/null +++ b/lib/services/windowsazure.rb @@ -0,0 +1,51 @@ +class Service::WindowsAzure < Service::HttpPost + string :hostname, :username, :password + + white_list :hostname, :username + + default_events :push + + url "https://www.windowsazure.com/" + logo_url "https://www.windowsazure.com/css/images/logo.png" + + maintained_by :github => "suwatch", + :twitter => "@suwat_ch" + + supported_by :web => "https://github.com/projectkudu/kudu/wiki", + :email => "davidebb@microsoft.com", + :twitter => "@davidebbo" + + def receive_event + hostname = required_config_value("hostname").to_s.strip + username = required_config_value("username").to_s.strip + password = required_config_value("password").to_s.strip + + raise_config_error "Invalid hostname" if hostname.empty? + raise_config_error "Invalid username" if username.empty? + raise_config_error "Invalid password" if password.empty? + + http.ssl[:verify] = false + http.headers['X-GitHub-Event'] = event.to_s + + http.basic_auth(username, password) + + url = "https://#{hostname}:443/deploy?scmType=GitHub" + + res = deliver url + raise_config_error "Invalid HTTP Response: #{res.status}" if res.status < 200 || res.status >= 400 + end + + def original_body + payload + end + + def default_encode_body + encode_body_as_form + end + + def encode_body_as_form + http.headers["content-type"] = "application/x-www-form-urlencoded" + Faraday::Utils.build_nested_query( + http.params.merge(:payload => generate_json(original_body))) + end +end diff --git a/lib/services/xmpp_base.rb b/lib/services/xmpp_base.rb new file mode 100644 index 000000000..c78ac7239 --- /dev/null +++ b/lib/services/xmpp_base.rb @@ -0,0 +1,184 @@ +class XmppHelper < Service + + def receive_event + check_config data + + commit_branch = (payload['ref'] || '').split('/').last || '' + filter_branch = data['filter_branch'].to_s + + # If filtering by branch then don't make a post + if (filter_branch.length > 0) && (commit_branch.index(filter_branch) == nil) + return false + end + + return false if event.to_s =~ /fork/ && config_boolean_false?('notify_fork') + return false if event.to_s =~ /watch/ && config_boolean_false?('notify_watch') + return false if event.to_s =~ /_comment/ && config_boolean_false?('notify_comments') + return false if event.to_s =~ /gollum/ && config_boolean_false?('notify_wiki') + return false if event.to_s =~ /issue/ && config_boolean_false?('notify_issue') + return false if event.to_s =~ /pull_/ && config_boolean_false?('notify_pull') + + build_message(event, payload) + return true + end + + def check_port(data) + return 5222 if data['port'].to_s.empty? + begin + return Integer(data['port']) + rescue Exception => e + raise_config_error 'XMPP port must be numeric' + end + end + + def check_host(data) + return nil if data['host'].to_s.empty? + return data['host'].to_s + end + + def build_message(event, payload) + case event + when :push + messages = [] + messages << "#{push_summary_message}: #{url}" + messages += distinct_commits.first(3).map { + |commit| self.format_commit_message(commit) + } + send_messages messages + when :commit_comment + send_messages "#{commit_comment_summary_message} #{url}" + when :issue_comment + send_messages "#{issue_comment_summary_message} #{url}" + when :issues + send_messages "#{issue_summary_message} #{url}" + when :pull_request + send_messages "#{pull_request_summary_message} #{url}" if action =~ /(open)|(close)/ + when :pull_request_review_comment + send_messages "#{pull_request_review_comment_summary_message} #{url}" + when :gollum + messages = [] + messages << "#{gollum_summary_message} #{url}" + pages.first(3).map { + | page | messages << self.format_wiki_page_message(page) + } + send_messages messages + end + end + + def url + shorten_url(summary_url) if not @data['is_test'] + summary_url + end + + def gollum_summary_message + num = pages.length + "[#{payload['repository']['name']}] @#{sender.login} modified #{num} page#{num != 1 ? 's' : ''}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def format_wiki_page_message(page) + url = page['html_url'] + url = shorten_url(url) if not @data['is_test'] + "User #{page['action']} page \"#{page['title']}\" #{url}" + end + + def push_summary_message + message = [] + message << "[#{repo_name}] @#{pusher_name}" + + if created? + if tag? + message << "tagged #{tag_name} at" + message << (base_ref ? base_ref_name : after_sha) + else + message << "created #{branch_name}" + + if base_ref + message << "from #{base_ref_name}" + elsif distinct_commits.empty? + message << "at #{after_sha}" + end + + num = distinct_commits.size + message << "(+#{num} new commit#{num != 1 ? 's' : ''})" + end + + elsif deleted? + message << "deleted #{branch_name} at #{before_sha}" + + elsif forced? + message << "force-pushed #{branch_name} from #{before_sha} to #{after_sha}" + + elsif commits.any? and distinct_commits.empty? + if base_ref + message << "merged #{base_ref_name} into #{branch_name}" + else + message << "fast-forwarded #{branch_name} from #{before_sha} to #{after_sha}" + end + + else + num = distinct_commits.size + message << "pushed #{num} new commit#{num != 1 ? 's' : ''} to #{branch_name}" + end + + message.join(' ') + end + + def format_commit_message(commit) + short = commit['message'].split("\n", 2).first.to_s + short += '...' if short != commit['message'] + + author = commit['author']['name'] + sha1 = commit['id'] + files = Array(commit['modified']) + #dirs = files.map { |file| File.dirname(file) }.uniq + + "#{repo_name}/#{branch_name} #{sha1[0..6]} " + + "#{commit['author']['name']}: #{short}" + end + + def issue_summary_message + "[#{repo.name}] @#{sender.login} #{action} issue \##{issue.number}: #{issue.title}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def issue_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + "[#{repo.name}] @#{sender.login} commented on issue \##{issue.number}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def commit_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + sha1 = comment.commit_id + "[#{repo.name}] @#{sender.login} commented on commit #{sha1[0..6]}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def pull_request_summary_message + base_ref = pull.base.label.split(':').last + head_ref = pull.head.label.split(':').last + head_label = head_ref != base_ref ? head_ref : pull.head.label + + "[#{repo.name}] @#{sender.login} #{action} pull request " + + "\##{pull.number}: #{pull.title} (#{base_ref}...#{head_ref})" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end + + def pull_request_review_comment_summary_message + short = comment.body.split("\r\n", 2).first.to_s + short += '...' if short != comment.body + sha1 = comment.commit_id + "[#{repo.name}] @#{sender.login} commented on pull request " + + "\##{pull_request_number} #{sha1[0..6]}: #{short}" + rescue + raise_config_error "Unable to build message: #{$!.to_s}" + end +end \ No newline at end of file diff --git a/lib/services/xmpp_im.rb b/lib/services/xmpp_im.rb new file mode 100644 index 000000000..3af72f146 --- /dev/null +++ b/lib/services/xmpp_im.rb @@ -0,0 +1,84 @@ +require_relative 'xmpp_base' + +class Service::XmppIm < XmppHelper + + self.title = 'XMPP IM' + self.hook_name = 'xmpp_im' + + string :JID, :receivers, :host, :port + password :password + boolean :notify_fork, :notify_wiki, :notify_comments, + :notify_issue, :notify_watch, :notify_pull + + white_list :filter_branch, :JID, :receivers + + default_events :push, :commit_comment, :issue_comment, + :issues, :pull_request, :pull_request_review_comment, + :gollum + + def send_messages(messages) + messages = Array(messages) + setup_connection() + @receivers.each do |receiver| + messages.each do |message| + @client.send ::Jabber::Message::new(receiver, message) + end + end + ensure + @client.close if @client + end + + def setup_connection + if (@client.nil?) + begin + @client = ::Jabber::Client.new(::Jabber::JID::new(@data['JID'])) + @client.connect(@data['host'], @data['port']) + @client.auth(@data['password']) + ::Jabber::debug = true + rescue ::Jabber::ErrorResponse + raise_config_error 'Error response' + rescue ::Jabber::ClientAuthenticationFailure + raise_config_error 'Authentication error' + rescue ::Jabber::JabberError + raise_config_error "XMPP Error: #{$!.to_s}" + rescue StandardError => e + raise_config_error "Unknown error: #{$!.to_s}" + end + end + @client + end + + def set_connection(client) + @client = client + end + + def check_config(data) + raise_config_error 'JID is required' if data['JID'].to_s.empty? + raise_config_error 'Password is required' if data['password'].to_s.empty? + raise_config_error 'Receivers list is required' if data['receivers'].to_s.empty? + @receivers = Array.new + data['receivers'].split().each do |jid| + begin + @receivers.push(::Jabber::JID.new(jid)) + rescue Exception => e + raise_config_error 'Illegal receiver JID' + end + end + data['port'] = check_port(data) + data['host'] = check_host(data) + @data = data + end + + url 'http://xmpp.org/rfcs/rfc6121.html' + logo_url 'http://xmpp.org/images/xmpp-small.png' + + # lloydwatkin on GitHub is pinged contacted for any bugs with the Hook code. + maintained_by :github => 'lloydwatkin' + + # Support channels for user-level Hook problems (service failure, + # misconfigured + supported_by :web => 'http://github.com/lloydwatkin/github-services/issues', + :email => 'lloyd@evilprofessor.co.uk', + :twitter => 'lloydwatkin', + :github => 'lloydwatkin' +end diff --git a/lib/services/xmpp_muc.rb b/lib/services/xmpp_muc.rb new file mode 100644 index 000000000..3fe85293a --- /dev/null +++ b/lib/services/xmpp_muc.rb @@ -0,0 +1,83 @@ +require_relative 'xmpp_base' + +class Service::XmppMuc < XmppHelper + + self.title = 'XMPP MUC' + self.hook_name = 'xmpp_muc' + + string :JID, :room, :server, :nickname, :host, :port + password :password, :room_password + boolean :notify_fork, :notify_wiki, :notify_comments, + :notify_issue, :notify_watch, :notify_pull + + white_list :room, :filter_branch, :JID, :room, :server, :nickname + + default_events :push, :commit_comment, :issue_comment, + :issues, :pull_request, :pull_request_review_comment, + :gollum + + def send_messages(messages) + messages = Array(messages) + setup_muc_connection() + messages.each do |message| + @muc.send ::Jabber::Message::new(::Jabber::JID.new(@data['muc_room']), message) + end + @muc.exit + ensure + @client.close if @client + end + + def setup_muc_connection + if (@muc.nil?) + begin + @client = ::Jabber::Client.new(::Jabber::JID::new(@data['JID'])) + @client.connect(@data['host'], @data['port']) + @client.auth(@data['password']) + ::Jabber::debug = true + @muc = ::Jabber::MUC::MUCClient.new(@client) + @muc.join(::Jabber::JID.new(@data['muc_room']), @data['room_password']) + rescue ::Jabber::ErrorResponse + raise_config_error 'Error response' + rescue ::Jabber::ClientAuthenticationFailure + raise_config_error 'Authentication error' + rescue ::Jabber::JabberError + raise_config_error "XMPP Error: #{$!.to_s}" + rescue StandardError => e + raise_config_error "Unknown error: #{$!.to_s}" + end + end + @muc + end + + def set_muc_connection(muc) + @muc = muc + end + + def check_config(data) + raise_config_error 'JID is required' if data['JID'].to_s.empty? + raise_config_error 'Password is required' if data['password'].to_s.empty? + raise_config_error 'Room is required' if data['room'].to_s.empty? + raise_config_error 'Server is required' if data['server'].to_s.empty? + data['nickname'] = 'github' if data['nickname'].to_s.empty? + data.delete(:room_password) if data['room_password'].to_s.empty? + data['muc_room'] = "#{data['room']}@#{data['server']}/#{data['nickname']}" + + data['port'] = check_port(data) + data['host'] = check_host(data) + + @data = data + end + + url 'http://xmpp.org/extensions/xep-0045.html' + logo_url 'http://xmpp.org/images/xmpp-small.png' + + # lloydwatkin on GitHub is pinged contacted for any bugs with the Hook code. + maintained_by :github => 'lloydwatkin' + + # Support channels for user-level Hook problems (service failure, + # misconfigured + supported_by :web => 'http://github.com/lloydwatkin/github-services/issues', + :email => 'lloyd@evilprofessor.co.uk', + :twitter => 'lloydwatkin', + :github => 'lloydwatkin' +end diff --git a/lib/services/you_track.rb b/lib/services/you_track.rb new file mode 100644 index 000000000..6b7c74c01 --- /dev/null +++ b/lib/services/you_track.rb @@ -0,0 +1,155 @@ +class Service::YouTrack < Service + string :base_url, :committers, :username, :branch + boolean :process_distinct + password :password + white_list :base_url, :username, :committers, :branch + + default_events :push, :pull_request + + url 'http://http://www.jetbrains.com/youtrack' + logo_url 'http://www.jetbrains.com/img/logos/YouTrack_logo_press_materials.gif' + + maintained_by :github => 'anna239' + supported_by :web => 'http://www.jetbrains.com/support/youtrack', + :email => 'youtrack-feedback@jetbrains.com', + :twitter => 'youtrack' + + + def receive_push + # If branch is defined by user setting, process commands only if commits + # are on that branch. If branch is not defined, process regardless of branch. + return unless active_branch? + + http.ssl[:verify] = false + http.url_prefix = data['base_url'] + payload['commits'].each { |c| process_commit(c) } + end + + def receive_pull_request + return unless payload['action'] == 'closed' + + http.ssl[:verify] = false + http.url_prefix = data['base_url'] + + process_pull_request + end + + def active_branch? + pushed_branch = payload['ref'].to_s[/refs\/heads\/(.*)/, 1] + active_branch = data['branch'].to_s + active_branch.empty? or active_branch.split(' ').include?(pushed_branch) + end + + def login + @logged_in ||= begin + api_key = data['api_key'] + if api_key.nil? + res = http_post 'rest/user/login' do |req| + req.params.update \ + :login => data['username'], + :password => data['password'] + req.headers['Content-Length'] = '0' + end + verify_response(res) + + http.headers['Cookie'] = res.headers['set-cookie'] + else + http.headers['X-YouTrack-ApiKey'] = api_key + end + http.headers['Cache-Control'] = 'no-cache' + true + end + end + + def process_commit(commit) + author = nil + + #If only distinct commits should be processed, check this + return unless commit['distinct'] or config_boolean_false?('process_distinct') + + commit['message'].split("\n").each { |commit_line| + issue_id, command = parse_message(commit_line) + next if issue_id.nil? + + login + # lazily load author + author ||= find_user_by_email(commit['author']['email']) + return if author.nil? + + command = 'comment' if command.nil? + comment_string = "Commit made by '''" + commit['author']['name'] + "''' on ''" + commit['timestamp'] + "''\n" + commit['url'] + "\n\n{quote}" + commit['message'].to_s + '{quote}' + execute_command(author, issue_id, command, comment_string) + } + end + + def process_pull_request + login + sender = payload['sender'] + author = find_user_by_email(sender['email']) + return if author.nil? + + request = payload['pull_request'] + request['body'].split("\n").each { |line| + issue_id, command = parse_message(line) + next if issue_id.nil? + + comment = "Pull request accepted by '''" + sender['login'] + "'''\n" + request['html_url'] + "\n\n{quote}" + request['body'].to_s + '{quote}' + execute_command(author, issue_id, command, comment) + } + + end + + def find_user_by_email(email) + counter = 0 + found_user = nil + while true + body = '' + res = http_get 'rest/admin/user', :q => email, :group => data['committers'], :start => counter + verify_response(res) + xml_body = REXML::Document.new(res.body) + xml_body.root.each_element do |user_ref| + res = http_get "rest/admin/user/#{user_ref.attributes['login']}" + verify_response(res) + attributes = REXML::Document.new(res.body).root.attributes + if attributes['email'].upcase == email.upcase || (attributes['jabber'] ? attributes['jabber'].upcase == email.upcase : false) + return if !found_user.nil? + found_user = user_ref.attributes['login'] + end + end + return found_user if xml_body.root.elements.size < 10 + counter += 10 + end + end + + def execute_command(author, issue_id, command, comment_string) + res = http_post "rest/issue/#{issue_id}/execute" do |req| + req.params[:command] = command unless command.nil? + req.params[:comment] = comment_string + req.params[:runAs] = author + end + verify_response(res) + end + + def verify_response(res) + case res.status + when 200..299 + when 403, 401, 422 then + raise_config_error('Invalid Credentials') + when 404, 301, 302 then + raise_config_error('Invalid YouTrack URL') + else + raise_config_error("HTTP: #{res.status}") + end + end + + def parse_message(message) + issue_id = message[/( |^)#(\w+-\d+)\b/, 2] + return nil, nil if issue_id.nil? + + command = message[/( |^)#\w+-\d+ (.+)/, 2] + command.strip! unless command.nil? + + return issue_id, command + end + +end diff --git a/lib/services/zendesk.rb b/lib/services/zendesk.rb new file mode 100644 index 000000000..c999f0365 --- /dev/null +++ b/lib/services/zendesk.rb @@ -0,0 +1,50 @@ +class Service::Zendesk < Service + default_events :commit_comment, :issues, :issue_comment, :pull_request, :push + string :subdomain, :username + password :password + white_list :subdomain, :username + + def invalid_request? + data['username'].to_s.empty? or + data['password'].to_s.empty? or + data['subdomain'].to_s.empty? + end + + def service_url(subdomain, ticket_id) + if subdomain =~ /\./ + url = "https://#{subdomain}/api/v2/integrations/github?ticket_id=#{ticket_id}" + else + url = "https://#{subdomain}.zendesk.com/api/v2/integrations/github?ticket_id=#{ticket_id}" + end + + begin + Addressable::URI.parse(url) + rescue Addressable::URI::InvalidURIError + raise_config_error("Invalid subdomain #{subdomain}") + end + + url + end + + def receive_event + raise_config_error "Missing or bad configuration" if invalid_request? + + if payload.inspect =~ /zd#(\d+)/i + ticket_id = $1 + else + return + end + + http.basic_auth(data['username'], data['password']) + http.headers['Content-Type'] = 'application/json' + http.headers['Accept'] = 'application/json' + http.headers['X-GitHub-Event'] = event.to_s + + url = service_url(data['subdomain'], ticket_id) + res = http_post(url, generate_json(:payload => payload)) + + if res.status != 201 + raise_config_error("Unexpected response code:#{res.status}") + end + end +end diff --git a/services/zohoprojects.rb b/lib/services/zohoprojects.rb similarity index 73% rename from services/zohoprojects.rb rename to lib/services/zohoprojects.rb index fd33da80c..0dfc0eeba 100644 --- a/services/zohoprojects.rb +++ b/lib/services/zohoprojects.rb @@ -1,12 +1,14 @@ class Service::ZohoProjects < Service - string :project_id, :token + string :project_id + password :token + white_list :project_id def receive_push res = http_post "https://projects.zoho.com/serviceHook", :pId => data['project_id'], :authtoken => data['token'], :scope => "projectsapi", - :payload => JSON.generate(payload) + :payload => generate_json(payload) if res.status != 200 raise_config_error end diff --git a/script/bootstrap b/script/bootstrap index 0c0c02084..c37b09a1c 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -1,117 +1,12 @@ -#!/usr/bin/env ruby -#/ Usage: script/bootstrap [] -#/ Bootstraps the gem environment. -#/ -#/ Options are passed through to the bundle-install command. In most cases you -#/ won't need these. They're used primarily in production environments. -#/ --local use gems in vendor/cache instead of rubygems.org -#/ --without= do not install gems in the groups specified -# -# ============================================================================= -# Uses bundler to install all gems specified in the Gemfile under vendor/gems, -# records the load path in config/loadpath, and generates bundler-free binstubs -# under bin. -# -# The basic idea is to use bundler to install necessary gems but not -# rely on it to manage the load path at runtime because it's slow. Requiring -# 'bundler/setup' takes ~500ms user CPU time in production and ~1500ms in -# development/test. This makes it unusable in scenarios that require a fast -# boot (e.g., script/gerve, proxymachine daemons, ernie/smoke, etc.). It's also -# a problem in development where it slows tools like rake command line -# completion to a crawl and adds at least a second to single-file test runs. -# -# There's very little reason to use bundler at runtime since everything -# is known at install time. We simply save off the result of the work done by -# bundle/setup and use it until bundle-install is run again. - -# show usage message with --help -if ARGV.include?('--help') - system "grep '^#/' <'#{__FILE__}' |cut -c4-" - exit 2 -end - -# go into the project root because it makes everything easier -root = File.expand_path('../..', __FILE__) -Dir.chdir(root) - -# point bundler to the right stuff -ENV['BUNDLE_GEMFILE'] = "#{root}/Gemfile" -ENV['BUNDLE_PATH'] = "#{root}/vendor/gems" - -# bring in rubygems and make sure bundler is installed. -require 'rubygems' -begin - require 'bundler' -rescue LoadError => boom - warn "Bundler not found. Install it with `gem install bundler' and try again." - exit 0 -end - -# record the Gemfile checksum so we can tell if the Gemfile has changed -# since our loadpath was last generated. this is used in config/basic.rb -# to verify the environment is bootstrapped and up-to-date. -checksum = `cksum Gemfile`.to_i -installed = File.read('.bundle/checksum').to_i rescue nil - -# run a quick check to see if everything's installed and up-to-date so we can -# skip the install and loadpath generation step if possible. -if checksum == installed && system('bundle check 1>/dev/null 2>&1') - puts "Gem environment up-to-date." -else - # run bundle-install to install any missing gems - argv = ['--no-color', 'install', '--path', 'vendor/gems'] + ARGV - system("bundle", *argv) || begin - warn "bundle executable not found. Ensure bundler is installed (`gem " + - "install bundler`) and that the gem bin path is in your PATH" - exit($?.exitstatus) - end - - # load the Gemfile - bundle = Bundler.setup - - # extract load paths for each gem and write to the config/loadpath file. - load_paths = [] - bundle.gems.each do |gem| - next if gem.name == 'bundler' - gem.load_paths.each do |path| - if path[0, root.size] == root - path = path[(root.size + 1), path.size] - load_paths << path - else - warn "external load path directory detected: #{path}" - end - end - end - - # move the loadpath and checksum files into place if everything was installed - # okay and the load path file was written successfully. - File.open('.bundle/loadpath+', 'wb') { |fd| fd.write(load_paths.join("\n")) } - File.rename('.bundle/loadpath+', '.bundle/loadpath') - File.open('.bundle/checksum', 'wb') { |fd| fd.puts(checksum) } - - # write binstubs for all executables. we can't use bundler's --binstubs option - # because the generated executables require 'bundler/setup'. the binstubs - # generated here require only config/basic.rb, which sets up the loadpath - # manually using the .bundle/loadpath file. - Dir.mkdir "bin" unless File.directory?("bin") - template = DATA.read - lineno = File.read(__FILE__).count("\n") - template.count("\n") - bundle.gems.each do |spec| - spec.executables.each do |executable| - script = eval('%Q{' + template + '}', binding, __FILE__, lineno) - File.open("bin/#{executable}+", 'wb') { |fd| fd.write(script) } - File.chmod 0755, "bin/#{executable}+" - File.rename("bin/#{executable}+", "bin/#{executable}") - end - end -end - -__END__ -#!/usr/bin/env #{RbConfig::CONFIG['ruby_install_name']} -# -# This file was generated by script/bootstrap. -root = File.expand_path('../..', __FILE__) -require "#\{root\}/config/load" -gem_path = "#{spec.full_gem_path.sub(root, "#\{root\}")}" -load File.join(gem_path, '#{spec.bindir}', '#{executable}') - +#!/bin/sh +# Usage: script/bootstrap +# Ensures all gems are in vendor/cache and installed locally. +set -e + +if [ "$(uname -s)" = "Darwin" ]; then + HOMEBREW_PREFIX="$(brew --prefix)" + bundle config --local build.eventmachine "--with-cppflags=-I$HOMEBREW_PREFIX/opt/openssl/include" +fi + +bundle install --binstubs --local --path=vendor/gems +bundle package --all diff --git a/script/cibuild b/script/cibuild new file mode 100755 index 000000000..35da41831 --- /dev/null +++ b/script/cibuild @@ -0,0 +1,11 @@ +#!/bin/sh +# Usage: script/test +# Runs the library's CI suite. + +test -d "/usr/share/rbenv/shims" && { + export PATH=/usr/share/rbenv/shims:$PATH + export RBENV_VERSION="2.1.7-github" +} + +script/bootstrap +bundle exec rake test diff --git a/script/console b/script/console new file mode 100755 index 000000000..d63f1f4f6 --- /dev/null +++ b/script/console @@ -0,0 +1,5 @@ +#!/bin/sh +# Usage: script/console +# Starts an IRB console with this library loaded. + +exec bundle exec irb -r ./config/console diff --git a/script/deliver_payload b/script/deliver_payload deleted file mode 100755 index 6e9288fbc..000000000 --- a/script/deliver_payload +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env ruby - -$LOAD_PATH.unshift *Dir["#{File.dirname(__FILE__)}/../vendor/**/lib"] - -require "json" - -unless ARGV[0] - puts "Usage: ./script/deliver_payload [service-name]" - exit 1 -end - -payload_file = File.new("docs/github_payload") -hash = eval(payload_file.read) - -data_json = JSON.generate(hash["data"]) -payload_json = JSON.generate(hash["payload"]) - -File.open( "docs/payload_data", 'w' ) { |f| f.write( "data=#{data_json}&payload=#{payload_json}" ) } -exec "curl --data-binary @docs/payload_data http://localhost:8080/#{ARGV[0]}/push" diff --git a/script/package b/script/package new file mode 100755 index 000000000..5851400ee --- /dev/null +++ b/script/package @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Usage: script/package +# Updates the gemspec and builds a new gem in the pkg directory. + +mkdir -p pkg +gem build *.gemspec +mv *.gem pkg diff --git a/script/test b/script/test new file mode 100755 index 000000000..fd1ac71df --- /dev/null +++ b/script/test @@ -0,0 +1,5 @@ +#!/bin/sh +# Usage: script/test +# Runs the library's test suite. + +bundle exec rake test diff --git a/script/update_github b/script/update_github new file mode 100755 index 000000000..945d6b117 --- /dev/null +++ b/script/update_github @@ -0,0 +1,7 @@ +#!/bin/sh +# Usage: script/update_github +# Updates the documentation for Services on GitHub.com + +# Expects github/github to be in the default Boxen location +# https://github.com/github/github-services/blob/master/Rakefile#L56-L58 +bundle exec rake services:build diff --git a/services/agilezen.rb b/services/agilezen.rb deleted file mode 100644 index ea03b40c5..000000000 --- a/services/agilezen.rb +++ /dev/null @@ -1,22 +0,0 @@ -class Service::AgileZen < Service - string :api_key, :project_id, :branches - - def receive_push - raise_config_error "Missing 'api_key'" if data['api_key'].to_s == '' - raise_config_error "Missing 'project_id'" if data['project_id'].to_s == '' - - branches = data['branches'].to_s.split(/\s+/) - ref = payload["ref"].to_s - return unless branches.empty? || branches.include?(ref.split("/").last) - - http.headers['X-Zen-ApiKey'] = data['api_key'] - http.headers['Content-Type'] = 'application/json' - - res = http_post "https://agilezen.com/api/v1/projects/#{data['project_id']}/changesets/github", - JSON.generate(payload) - - if res.status < 200 || res.status > 299 - raise_config_error - end - end -end diff --git a/services/amqp.rb b/services/amqp.rb deleted file mode 100644 index 18092f7e9..000000000 --- a/services/amqp.rb +++ /dev/null @@ -1,93 +0,0 @@ -class Service::AMQP < Service - string :server, :port, :vhost, :exchange, :username - password :password - - def receive_push - # Support for specifying as host or server - data['host'] ||= data['server'] - - if !data['host'] - raise_config_error "Invalid server host." - end - - if !data['exchange'] - raise_config_error "Invalid exchange." - end - - # Modify the commits a bit - payload['commits'].each do |commit| - commit['files'] = { - 'added' => commit['added'], - 'modified' => commit['modified'], - 'removed' => commit['removed'], - } - commit.delete('added') - commit.delete('modified') - commit.delete('removed') - end - - # Generate the push routing key - owner = payload['repository']['owner']['name'] - repo = payload['repository']['name'] - ref = ref_name - routing_key = "github.push.#{owner}.#{repo}.#{ref}" - - # Assemble the push message - msg = {} - msg['_meta'] = { - 'routing_key' => routing_key, - 'exchange' => data['exchange'], - } - msg['payload'] = payload - - # Publish the push message to the exchange - amqp_exchange.publish(msg.to_json, - :key => routing_key, - :content_type => 'application/json') - - # Publish individual commit messages - payload['commits'].each do |commit| - # Generate the commit routing key - author = commit['author']['email'] - routing_key = "github.commit.#{owner}.#{repo}.#{ref}.#{author}" - - # Assemble the commit message - msg = {} - msg['_meta'] = { - 'routing_key' => routing_key, - 'exchange' => data['exchange'], - } - msg['payload'] = commit - - # Publish the commit message to the exchange - amqp_exchange.publish(msg.to_json, - :key => routing_key, - :content_type => 'application/json') - end - - amqp_connection.close - end - - attr_writer :amqp_connection - attr_writer :amqp_exchange - - def amqp_exchange - @amqp_exchange ||= MQ::Exchange.new(amqp_channel, - :topic, - data['exchange'], - :durable => true) - end - - def amqp_channel - @amqp_channel ||= MQ.new(amqp_connection) - end - - def amqp_connection - @amqp_connection ||= ::AMQP.connect(:host => data['host'], - :port => data['port'] || 5672, - :user => data['username'] || 'guest', - :pass => data['password'] || 'guest', - :vhost => data['vhost'] || '/', - :logging => false) - end -end diff --git a/services/basecamp.rb b/services/basecamp.rb deleted file mode 100644 index f9fcf6b45..000000000 --- a/services/basecamp.rb +++ /dev/null @@ -1,140 +0,0 @@ -class Service::Basecamp < Service - string :url, :project, :category, :username - password :password - boolean :ssl - - def receive_push - raise_config_error "Invalid basecamp domain" if basecamp_domain.nil? - - repository = payload['repository']['name'] - name_with_owner = File.join(payload['repository']['owner']['name'], repository) - branch = ref_name - - commits = payload['commits'].reject { |commit| commit['message'].to_s.strip == '' } - return if commits.empty? - - ::Basecamp.establish_connection! basecamp_domain, - data['username'], data['password'], data['ssl'].to_i == 1 - - commits.each do |commit| - gitsha = commit['id'] - short_git_sha = gitsha[0..5] - timestamp = Date.parse(commit['timestamp']) - - added = commit['added'].map { |f| ['A', f] } - removed = commit['removed'].map { |f| ['R', f] } - modified = commit['modified'].map { |f| ['M', f] } - changed_paths = (added + removed + modified).sort_by { |(char, file)| file } - changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n ") - - # Shorten the elements of the subject - commit_title = commit['message'][/^([^\n]+)/, 1] - if commit_title.length > 50 - commit_title = commit_title.slice(0,50) << '...' - end - - title = "Commit on #{name_with_owner}: #{short_git_sha}: #{commit_title}" - - body = <<-EOH -*Author:* #{commit['author']['name']} <#{commit['author']['email']}> -*Commit:* #{gitsha} -*Date:* #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')}) -*Branch:* #{branch} -*Home:* #{payload['repository']['url']} - -h2. Log Message - -
#{commit['message']}
-EOH - - if changed_paths.size > 0 - body << <<-EOH - -h2. Changed paths - -
  #{changed_paths}
-EOH - end - - post_message :title => title, :body => body - end - - rescue SocketError => boom - if boom.to_s =~ /getaddrinfo: Name or service not known/ - raise_config_error "Invalid basecamp domain name" - else - raise - end - rescue ActiveResource::UnauthorizedAccess => boom - raise_config_error "Unauthorized. Verify the project URL and credentials." - rescue ActiveResource::ForbiddenAccess => boom - raise_config_error boom.to_s - rescue ActiveResource::Redirection => boom - raise_config_error "Invalid project URL: #{boom}" - rescue RuntimeError => boom - if boom.to_s =~ /\((?:403|401|422)\)/ - raise_config_error "Invalid credentials: #{boom}" - elsif boom.to_s =~ /\((?:404|301)\)/ - raise_config_error "Invalid project URL: #{boom}" - elsif boom.to_s == 'Unprocessable Entity (422)' - # do nothing - else - raise - end - end - - attr_writer :basecamp - attr_writer :project_id - attr_writer :category_id - - def basecamp_domain - @basecamp_domain ||= Addressable::URI.parse(data['url']).host - end - - def build_message(options = {}) - m = ::Basecamp::Message.new :project_id => project_id - m.category_id = category_id - options.each do |key, value| - m.send "#{key}=", value - end - m - end - - def post_message(options = {}) - build_message(options).save - end - - def all_projects - Array(::Basecamp::Project.all) - end - - def all_categories - Array(::Basecamp::Category.post_categories(project_id)) - end - - def project_id - @project_id ||= begin - name = data['project'].to_s - name.downcase! - projects = all_projects.select { |p| p.name.downcase == name } - case projects.size - when 1 then projects.first.id - when 0 then raise_config_error("Invalid Project: #{name.downcase}") - else raise_config_error("Multiple projects named: #{name.downcase}") - end - end - end - - def category_id - @category_id ||= begin - name = data['category'].to_s - name.downcase! - categories = all_categories.select { |c| c.name.downcase == name } - case categories.size - when 1 then categories.first.id - when 0 then raise_config_error("Invalid Category: #{name.downcase}") - else raise_config_error("Multiple categories named: #{name.downcase}") - end - end - end -end diff --git a/services/boxcar.rb b/services/boxcar.rb deleted file mode 100644 index 00d2c747a..000000000 --- a/services/boxcar.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Service::Boxcar < Service - string :subscribers - - def receive_push - http_post \ - "http://providers.boxcar.io/github/%s" % - [secrets['boxcar']['apikey']], - :emails => data['subscribers'], - :payload => JSON.generate(payload) - end -end diff --git a/services/bugherd.rb b/services/bugherd.rb deleted file mode 100644 index 0404d435d..000000000 --- a/services/bugherd.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Service::Bugherd < Service - string :project_key - - def receive_push - if data['url'].present? - url = data['url'] - else - url = "http://www.bugherd.com/github_web_hook/#{data['project_key']}" - end - http_post url, - :payload => JSON.generate(payload) - end -end diff --git a/services/bugly.rb b/services/bugly.rb deleted file mode 100644 index 2bc9aa866..000000000 --- a/services/bugly.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Service::Bugly < Service - string :project_id, :account_name, :token - - def receive_push - http.ssl[:verify] = false # :( - http_post "https://#{data['account_name']}.bug.ly/changesets.json?service=github&project_id=#{data['project_id']}", - JSON.generate(payload), - 'X-BuglyToken' => data['token'], - 'Content-Type' => 'application/json' - return - end -end diff --git a/services/co_op.rb b/services/co_op.rb deleted file mode 100644 index 1f0117001..000000000 --- a/services/co_op.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Service::CoOp < Service - string :group_id, :token - - self.title = 'Co-Op' - - def receive_push - repository = payload['repository']['name'] - payload['commits'].each do |commit| - status = "#{commit['author']['name']} just committed a change to #{repository} on GitHub: #{commit['message']} (#{commit['url']})" - res = http_post "http://coopapp.com/groups/%s/notes" % [data['group_id']], - {:status => status, :key => data['token']}.to_json, - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'GitHub Notifier' - - if res.status >= 400 - raise_config_error - end - end - end -end diff --git a/services/codeclimate.rb b/services/codeclimate.rb deleted file mode 100644 index eb98b5154..000000000 --- a/services/codeclimate.rb +++ /dev/null @@ -1,14 +0,0 @@ -class Service::CodeClimate < Service - string :token - - def receive_push - http.ssl[:verify] = false - http.basic_auth "github", token - http_post "https://codeclimate.com/github_pushes", :payload => payload.to_json - end - - def token - data["token"].to_s.strip - end - -end diff --git a/services/convore.rb b/services/convore.rb deleted file mode 100644 index 4ecf164be..000000000 --- a/services/convore.rb +++ /dev/null @@ -1,63 +0,0 @@ -class Service::Convore < Service - string :topic_id, :username - password :password - - def receive_push - raise_config_error "Missing username" if data['username'].to_s == '' - - repository = payload['repository']['name'] - owner = payload['repository']['owner']['name'] - branch = branch_name - commits = payload['commits'] - compare_url = payload['compare'] - commits.reject! { |commit| commit['message'].to_s.strip == '' } - return if commits.empty? - - prefix = "[#{repository}/#{branch}]" - primary, others = commits[0..4], Array(commits[5..-1]) - messages = - primary.map do |commit| - short = commit['message'].split("\n", 2).first - short += ' ...' if short != commit['message'] - "#{prefix} #{short} - #{commit['author']['name']}" - end - - if messages.size > 1 - before, after = payload['before'][0..6], payload['after'][0..6] - url = compare_url - summary = - if others.any? - "#{prefix} (+#{others.length} more) commits #{before}...#{after}: #{url}" - else - "#{prefix} commits #{before}...#{after}: #{url}" - end - messages << summary - else - url = commits.first['url'] - messages[0] = "#{messages.first} (#{url})" - end - - http.url_prefix = "https://convore.com/api/topics" - http.basic_auth data['username'], data['password'] - - begin - messages.each do |line| - res = speak(data['topic_id'], line) - if res.status < 200 or res.status > 299 - raise_config_error "Convore Error" - end - - body = JSON.parse(res.body) - raise_config_error "Convore Error" if body.include?("error") - end - - rescue Faraday::Error::ConnectionFailed - raise_config_error "Connection refused. Invalid group." - end - end - - def speak(topic_id, line) - http_post "#{data['topic_id']}/messages/create.json", - :message => line - end -end diff --git a/services/cube.rb b/services/cube.rb deleted file mode 100644 index c8bbae819..000000000 --- a/services/cube.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Service::Cube < Service - string :domain, :project, :token - - def receive_push - http_post "http://cube.bitrzr.com/integration/events/github/create", - 'payload' => JSON.generate(payload), - 'project_name' => data['project'], - 'project_token' => data['token'], - 'domain' => data['domain'] - end -end diff --git a/services/ducksboard.rb b/services/ducksboard.rb deleted file mode 100644 index 8c3be1fca..000000000 --- a/services/ducksboard.rb +++ /dev/null @@ -1,57 +0,0 @@ -class Service::Ducksboard < Service - string :webhook_key - - default_events :push, :issues, :fork, :watch - - # don't feed more than 5 ducksboard webhook endpoints with an event - DUCKS_MAX_KEYS = 5 - - def receive_push - # Build Ducksboard webhook urls from the webhook_key config param, - # then send a JSON containing the event type and payload to such - # url. - - http.headers['content-type'] = 'application/x-www-form-urlencoded' - body = http.params.merge( - :content => JSON.generate(:event => event, :payload => payload)) - - parse_webhook_key(data).each do |key| - url = "https://webhooks.ducksboard.com/#{key}" - http_post url, body - end - - rescue EOFError - raise_config_error "Invalid server response." - end - - alias receive_issues receive_push - alias receive_fork receive_push - alias receive_watch receive_push - - def parse_webhook_key(data) - # the webhook key param is required - if !data['webhook_key'] - raise_config_error "Invalid webhook key" - end - - # we accept many webhook keys separated by spaces, but never more - # than DUCKS_MAX_KEYS - keys = data['webhook_key'].split[0, DUCKS_MAX_KEYS].collect do |key| - # we do accept ducksboard webhook urls from which the key - # will be extracted - if key =~ /^https\:\/\/webhooks\.ducksboard\.com\// - key = URI.parse(key).path[1..-1] - end - - # only alphanumeric hex keys are valid - if key !~ /^[a-fA-F0-9]+$/ - raise_config_error "Invalid webhook key" - end - - key - end - - keys - end - -end diff --git a/services/email.rb b/services/email.rb deleted file mode 100644 index d05c35c0f..000000000 --- a/services/email.rb +++ /dev/null @@ -1,273 +0,0 @@ -class Net::SMTP - def mailfrom(from_addr) - from_addr = from_addr[/<([^>]+)>/, 1] if from_addr.include?('<') - - if $SAFE > 0 - raise SecurityError, 'tainted from_addr' if from_addr.tainted? - end - getok("MAIL FROM:<#{from_addr}>") - end -end - -class Service::Email < Service - string :address, :secret - boolean :send_from_author - - def receive_push - configure_mail_defaults unless mail_configured? - - addresses.each do |address| - send_message_to address - end - end - - def send_message_to(address) - send_mail mail_message(address) - end - - def send_mail(mail) - mail.deliver! - end - - def configure_mail_defaults - my = self - - Mail.defaults do - delivery_method :smtp, - :address => my.smtp_address, - :port => my.smtp_port, - :domain => my.smtp_domain, - :user_name => my.smtp_user_name, - :password => my.smtp_password, - :authentication => my.smtp_authentication, - :enable_starttls_auto => my.smtp_enable_starttls_auto?, - :openssl_verify_mode => my.smtp_openssl_verify_mode - end - - mail_configured! - end - - def mail_configured? - defined?(@@mail_configured) && @@mail_configured - end - - def mail_configured! - @@mail_configured = true - end - - def addresses - data['address'].split(' ').slice(0, 2) - end - - def mail_message(address) - my = self - - Mail.new do - to address - from my.from_address - reply_to my.from_address - subject my.mail_subject - headers my.secret_header - - text_part do - content_type 'text/plain; charset=UTF-8' - body my.text_body - end - end - end - - - def text_body - body = commits.inject(repository_text) do |text, commit| - text << commit_text(commit) - end - - body << compare_text unless single_commit? - - body - end - - def repository_text - align(<<-EOH) - Branch: #{branch_ref} - Home: #{repo_url} - EOH - end - - def commit_text(commit) - gitsha = commit['id'] - added = commit['added'].map { |f| ['A', f] } - removed = commit['removed'].map { |f| ['R', f] } - modified = commit['modified'].map { |f| ['M', f] } - - changed_paths = (added + removed + modified).sort_by { |(char, file)| file } - changed_paths = changed_paths.collect { |entry| entry * ' ' }.join("\n ") - - timestamp = Date.parse(commit['timestamp']) - - commit_author = "#{commit['author']['name']} <#{commit['author']['email']}>" - - text = align(<<-EOH) - Commit: #{gitsha} - #{commit['url']} - Author: #{commit_author} - Date: #{timestamp} (#{timestamp.strftime('%a, %d %b %Y')}) - - EOH - - if changed_paths.size > 0 - text << align(<<-EOH) - Changed paths: - #{changed_paths} - - EOH - end - - text << align(<<-EOH) - Log Message: - ----------- - #{commit['message']} - - - EOH - - text - end - - def compare_text - "Compare: #{payload['compare']}" - end - - def single_commit? - first_commit == last_commit - end - - def branch_ref - payload['ref'] - end - - def repo_url - payload['repository']['url'] - end - - def mail_subject - if first_commit - "[#{name_with_owner}] #{first_commit_sha.slice(0, 6)}: #{first_commit_title}" - else - "[#{name_with_owner}]" - end - end - - def secret_header - secret ? {'Approved' => secret} : {} - end - - def from_address - send_from_author? ? author_address : noreply_address - end - - def send_from_author? - data['send_from_author'] - end - - def author_address - "#{author_name} <#{author_email}>" - end - - def author - commit = last_commit || {} - commit['author'] || commit['committer'] || payload['pusher'] - end - - def author_name - author['name'] - end - - def author_email - author['email'] - end - - def last_commit - payload['commits'].last # assume that the last committer is also the pusher - end - - def secret - data['secret'] if data['secret'].to_s.size > 0 - end - - def name_with_owner - File.join(owner_name, repository_name) - end - - def owner_name - payload['repository']['owner']['name'] - end - - def repository_name - payload['repository']['name'] - end - - def first_commit_sha - first_commit['id'] - end - - def first_commit_title(limit = 50) - title_line = first_commit['message'][/\A[^\n]+/] || '' - - title_line.length > limit ? shorten(title_line, limit) : title_line - end - - def shorten(text, limit) - text.slice(0, limit) << '...' - end - - def first_commit - payload['commits'].first - end - - def align(text, indent = ' ') - margin = text[/\A\s+/].size - - text.gsub(/^\s{#{margin}}/, indent) - end - - def smtp_address - @smtp_address ||= email_config['address'] - end - - def smtp_port - @smtp_port ||= (email_config['port'] || 25).to_i - end - - def smtp_domain - @smtp_domain ||= email_config['domain'] || 'localhost.localdomain' - end - - def smtp_authentication - @smtp_authentication ||= email_config['authentication'] - end - - def smtp_user_name - @smtp_user_name ||= email_config['user_name'] - end - - def smtp_password - @smtp_password ||= email_config['password'] - end - - def smtp_enable_starttls_auto? - @smtp_enable_starttls_auto ||= (email_config['enable_starttls_auto'] && true) - end - - def smtp_openssl_verify_mode - @smtp_openssl_verify_mode ||= email_config['openssl_verify_mode'] - end - - def smtp_logging? - @smtp_logging ||= email_config['enable_logging'] - end - - def noreply_address - @noreply_address ||= email_config['noreply_address'] || "GitHub " - end -end diff --git a/services/flowdock.rb b/services/flowdock.rb deleted file mode 100644 index 98bdc0132..000000000 --- a/services/flowdock.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'uri' - -class Service::Flowdock < Service - default_events :commit_comment, :gollum, :issues, :issue_comment, :pull_request, :push - string :token - - def receive_event - raise_config_error "Missing token" if data['token'].to_s.empty? - - token = URI.escape(data['token'].to_s.gsub(/\s/, '')) - - http.headers['X-GitHub-Event'] = event.to_s - http.headers['content-type'] = 'application/json' - http_post "https://api.flowdock.com/v1/github/#{token}", JSON.generate(payload) - end -end diff --git a/services/freckle.rb b/services/freckle.rb deleted file mode 100644 index 09892f7a9..000000000 --- a/services/freckle.rb +++ /dev/null @@ -1,25 +0,0 @@ -class Service::Freckle < Service - string :subdomain, :project, :token - - def receive_push - entries, subdomain, token, project = - [], data['subdomain'].strip, data['token'].strip, data['project'].strip - - payload['commits'].each do |commit| - minutes = (commit["message"].split(/\s/).find { |item| /^f:/ =~ item } || '')[2,100] - next unless minutes - entries << { - :date => commit["timestamp"], - :minutes => minutes, - :description => commit["message"].gsub(/(\s|^)f:.*(\s|$)/, '').strip, - :url => commit['url'], - :project_name => project, - :user => commit['author']['email'] - } - end - - http.headers['Content-Type'] = 'application/json' - http_post "http://#{data['subdomain']}.letsfreckle.com/api/entries/import", - {:entries => entries, :token => data['token']}.to_json - end -end diff --git a/services/friend_feed.rb b/services/friend_feed.rb deleted file mode 100644 index d8aadbea9..000000000 --- a/services/friend_feed.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Service::FriendFeed < Service - string :nickname, :remotekey - - def receive_push - repository = payload['repository']['name'] - friendfeed_url = "http://friendfeed.com/api/share" - - payload['commits'].each do |commit| - title = "#{commit['author']['name']} just committed a change to #{repository} on GitHub" - comment = "#{commit['message']} (#{commit['id']})" - - http.basic_auth data['nickname'], data['remotekey'] - http_post friendfeed_url, - :title => title, :link => commit['url'], :comment => comment, :via => :github - end - end -end diff --git a/services/geocommit.rb b/services/geocommit.rb deleted file mode 100644 index 9837e2e81..000000000 --- a/services/geocommit.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Service::GeoCommit < Service - self.title = 'geocommit' - def receive_push - http.headers['Content-Type'] = 'application/githubpostreceive+json' - http_post 'http://hook.geocommit.com/api/github', - JSON.generate(payload) - end -end diff --git a/services/gitlive.rb b/services/gitlive.rb deleted file mode 100644 index dfd69986b..000000000 --- a/services/gitlive.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Service::GitLive < Service - self.title = 'gitlive' - def receive_push - http_post 'http://gitlive.com/hook', - :payload => JSON.generate(payload) - end -end diff --git a/services/grmble.rb b/services/grmble.rb deleted file mode 100644 index b3164e89e..000000000 --- a/services/grmble.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Service::Grmble < Service - string :room_api_url, :token - - def receive_push - http.url_prefix = data['room_api_url'].to_s - repository = payload[ 'repository' ][ 'name' ] - branch = branch_name - commits = payload[ 'commits' ] - - commits.each do |commit| - message = { - 'nickname' => 'github', - 'content' => "[#{repository}/#{branch}] #{commit['message']} - #{commit['author']['name']} #{commit['url']}", - 'apiKey' => data[ 'token' ], - 'type' => 'message', - } - - http_post 'msg', :message => JSON.generate(message) - end - end -end diff --git a/services/grove.rb b/services/grove.rb deleted file mode 100644 index e4ca9188c..000000000 --- a/services/grove.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Service::Grove < Service - string :channel_token - - def receive_push - raise_config_error "Missing channel token" if data['channel_token'].to_s.empty? - - token = data['channel_token'].to_s - - res = http_post "https://grove.io/api/services/github/#{token}", - :payload => JSON.generate(payload) - end -end diff --git a/services/hipchat.rb b/services/hipchat.rb deleted file mode 100644 index 24e537915..000000000 --- a/services/hipchat.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Service::HipChat < Service - string :auth_token, :room - boolean :notify - - default_events :commit_comment, :download, :fork, :fork_apply, :gollum, - :issues, :issue_comment, :member, :public, :pull_request, :push, :watch - - def receive_event - # make sure we have what we need - raise_config_error "Missing 'auth_token'" if data['auth_token'].to_s == '' - raise_config_error "Missing 'room'" if data['room'].to_s == '' - - http.headers['X-GitHub-Event'] = event.to_s - - res = http_post "https://api.hipchat.com/v1/webhooks/github", - :auth_token => data['auth_token'], - :room_id => data['room'], - :payload => JSON.generate(payload), - :notify => data['notify'] ? 1 : 0 - if res.status < 200 || res.status > 299 - raise_config_error - end - end -end diff --git a/services/irc.rb b/services/irc.rb deleted file mode 100644 index 62990d324..000000000 --- a/services/irc.rb +++ /dev/null @@ -1,120 +0,0 @@ -class Service::IRC < Service - string :server, :port, :room, :nick - password :password - boolean :ssl, :message_without_join, :no_colors, :long_url, :notice - - def receive_push - return if distinct_commits.empty? - - url = data['long_url'].to_i == 1 ? summary_url : shorten_url(summary_url) - - messages = [] - messages << "#{summary_message}: #{url}" - messages += commit_messages.first(3) - send_messages messages - end - - def receive_pull_request - return unless opened? - - url = data['long_url'].to_i == 1 ? summary_url : shorten_url(summary_url) - - send_messages "#{summary_message}: #{url}" - end - - alias receive_issues receive_pull_request - - def send_messages(messages) - rooms = data['room'].gsub(",", " ").split(" ").map{|room| room[0].chr == '#' ? room : "##{room}"} - botname = data['nick'].to_s.empty? ? "GitHub#{rand(200)}" : data['nick'] - command = data['notice'].to_i == 1 ? 'NOTICE' : 'PRIVMSG' - - self.puts "PASS #{data['password']}" if !data['password'].to_s.empty? - self.puts "NICK #{botname}" - self.puts "MSG NICKSERV IDENTIFY #{data['nickservidentify']}" if !data['nickservidentify'].to_s.empty? - self.puts "USER #{botname} 8 * :GitHub IRCBot" - - loop do - case self.gets - when / 00[1-4] #{Regexp.escape(botname)} / - break - when /^PING\s*:\s*(.*)$/ - self.puts "PONG #{$1}" - end - end - - without_join = data['message_without_join'] == '1' - rooms.each do |room| - room, pass = room.split("::") - self.puts "JOIN #{room} #{pass}" unless without_join - - Array(messages).each do |message| - self.puts "#{command} #{room} :#{message}" - end - - self.puts "PART #{room}" unless without_join - end - - self.puts "QUIT" - self.gets until self.eof? - rescue SocketError => boom - if boom.to_s =~ /getaddrinfo: Name or service not known/ - raise_config_error 'Invalid host' - elsif boom.to_s =~ /getaddrinfo: Servname not supported for ai_socktype/ - raise_config_error 'Invalid port' - else - raise - end - rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH - raise_config_error 'Invalid host' - rescue OpenSSL::SSL::SSLError - raise_config_error 'Host does not support SSL' - end - - def gets - irc.gets - end - - def eof? - irc.eof? - end - - def puts(*args) - irc.puts *args - end - - def irc - @irc ||= begin - socket = TCPSocket.open(data['server'], data['port']) - - if data['ssl'].to_i == 1 - ssl_context = OpenSSL::SSL::SSLContext.new - ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE - ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context) - ssl_socket.sync_close = true - ssl_socket.connect - socket = ssl_socket - end - - socket - end - end - - def format_commit_message(commit) - short = commit['message'].split("\n", 2).first.to_s - short += '...' if short != commit['message'] - - author = commit['author']['name'] - sha1 = commit['id'] - files = Array(commit['modified']) - dirs = files.map { |file| File.dirname(file) }.uniq - - if data['no_colors'].to_i == 1 - "#{repo_name}: #{branch_name} #{author} * " + - "#{sha1[0..6]} (#{files.size} files in #{dirs.size} dirs): #{short}" - else - "\002#{repo_name}:\002 \00307#{branch_name}\003 \00303#{author}\003 * " + - "\002#{sha1[0..6]}\002 (#{files.size} files in #{dirs.size} dirs): #{short}" - end - end -end diff --git a/services/jabber.rb b/services/jabber.rb deleted file mode 100644 index ccb510e71..000000000 --- a/services/jabber.rb +++ /dev/null @@ -1,76 +0,0 @@ -# Jabber::Simple does some insane kind of queueing if it thinks -# we are not in their buddy list (which is always) so messages -# never get sent before we disconnect. This forces the library -# to assume the recipient is a buddy. -class ::Jabber::Simple - def subscribed_to?(x); true; end -end - -# Default implementation of MUCClient uses blocked connection -class ::Jabber::MUC::MUCClient - def join(jid, password=nil) - raise "MUCClient already active" if active? - - @jid = (jid.kind_of?(::Jabber::JID) ? jid : ::Jabber::JID.new(jid)) - activate - - pres = ::Jabber::Presence.new - pres.to = @jid - pres.from = @my_jid - xmuc = ::Jabber::MUC::XMUC.new - xmuc.password = password - pres.add xmuc - - @stream.send pres - - self - end -end - -class Service::Jabber < Service - string :user - - def receive_push - # Accept any friend request - im.accept_subscriptions = true - - #Split multiple addresses into array, removing duplicates - recipients = data.has_key?('user') ? data['user'].split(',').each(&:strip!).uniq : [] - conferences = data.has_key?('muc') ? data['muc'].split(',').each(&:strip!).uniq : [] - messages = [] - messages << "#{summary_message}: #{summary_url}" - messages += commit_messages - message = messages.join("\n") - - deliver_messages(message, recipients) - - # temporarily disabled - #deliver_muc(message, conferences) if !conferences.empty? - end - - def deliver_messages(message, recipients) - recipients.each do |recipient| - im.deliver_deferred recipient, message, :chat - end - end - - def deliver_muc(message, conferences) - conferences.each do |conference| - muc = mucs[conference] - muc ||= mucs[conference] = ::Jabber::MUC::MUCClient.new(im.client) - muc.join(conference) unless muc.active?() - im.deliver_deferred conference, message, :groupchat - end - end - - def mucs - @@mucs ||= {} - end - - attr_writer :im - def im - @im || @@im ||= begin - ::Jabber::Simple.new(secrets['jabber']['user'], secrets['jabber']['password']) - end - end -end diff --git a/services/kickoff.rb b/services/kickoff.rb deleted file mode 100644 index 669c30294..000000000 --- a/services/kickoff.rb +++ /dev/null @@ -1,30 +0,0 @@ -class Service::Kickoff < Service - string :project_id, :project_token - - def receive_push - raise_config_error 'Missing project id' if data['project_id'].to_s.empty? - raise_config_error 'Missing project token' if data['project_token'].to_s.empty? - - messages = [] - messages << "#{summary_message}: #{summary_url}" - messages += commit_messages.first(8) - - if messages.first =~ /pushed 1 new commit/ - messages.shift # drop summary message - messages.first << " (#{distinct_commits.first['url']})" - end - - doc = REXML::Document.new("") - e = REXML::Element.new("message") - e.text = messages.join("\n") - doc.root.add(e) - e = REXML::Element.new("service") - e.text = "github" - doc.root.add(e) - - http_post "http://api.kickoffapp.com/projects/#{data['project_id']}/chat" do |req| - req.params[:token] = data['project_token'] - req.body = doc.to_s - end - end -end diff --git a/services/leanto.rb b/services/leanto.rb deleted file mode 100644 index a73ea9e60..000000000 --- a/services/leanto.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Service::Leanto < Service - string :token - - self.title = 'Lean-To' - - def receive_push - res = http_post "http://www.lean-to.com/api/%s/commit" % - [ data['token'] ], - {'payload' => payload.to_json} - - if res.status != 200 - raise_config_error - end - end -end diff --git a/services/loggly.rb b/services/loggly.rb deleted file mode 100644 index 9c1c34ad5..000000000 --- a/services/loggly.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Service::Loggly < Service - string :input_token - - def receive_push - http.headers['Content-Type'] = 'application/json' - url = "https://logs.loggly.com/inputs/#{data['input_token']}" - payload['commits'].each { |commit| http_post url, commit.to_json } - end -end diff --git a/services/masterbranch.rb b/services/masterbranch.rb deleted file mode 100644 index 0bd5fd8dc..000000000 --- a/services/masterbranch.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Service::Masterbranch < Service - def receive_push - http_post "http://webhooks.masterbranch.com/gh-hook", - :payload => payload.to_json - end -end - diff --git a/services/notifo.rb b/services/notifo.rb deleted file mode 100644 index 00e9abc74..000000000 --- a/services/notifo.rb +++ /dev/null @@ -1,33 +0,0 @@ -class Service::Notifo < Service - string :subscribers - - def receive_push - return if Array(payload['commits']).size == 0 - - subscribe_url = URI.parse('https://api.notifo.com/v1/subscribe_user') - http.basic_auth 'github', secrets['notifo']['apikey'] - http.url_prefix = "https://api.notifo.com/v1" - - data['subscribers'].gsub(/\s/, '').split(',').each do |subscriber| - http_post "subscribe_user", :username => subscriber - - commit = payload['commits'].last; - author = commit['author'] || {} - - if payload['commits'].length > 1 - extras = payload['commits'].length - 1 - http_post "send_notification", - 'to' => subscriber, - 'msg' => "#{author['name']}: \"#{commit['message'].slice(0,40)}\" (+#{extras} more commits)", - 'title' => "#{payload['repository']['name']}/#{ref_name}", - 'uri' => payload['compare'] - else - http_post "send_notification", - 'to' => subscriber, - 'msg' => "#{author['name']}: \"#{commit['message']}\"", - 'title' => "#{payload['repository']['name']}/#{ref_name}", - 'uri' => commit['url'] - end - end - end -end diff --git a/services/notifymyandroid.rb b/services/notifymyandroid.rb deleted file mode 100644 index e84790444..000000000 --- a/services/notifymyandroid.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Service::NMA < Service - string :apikey - self.title = 'Notify My Android' - - def receive_push - return unless payload['commits'] - - url = URI.parse('https://www.notifymyandroid.com/publicapi/notify') - repository = payload['repository']['url'].split("/") - event = [repository[-2], repository[-1]].join('/') - application = "GitHub" - description = "#{payload['commits'].length} commits pushed to #{application} (#{payload['commits'][-1]['id'][0..7]}..#{payload['commits'][0]['id'][0..7]}) - - Latest Commit by #{payload['commits'][-1]['author']['name']} - #{payload['commits'][-1]['id'][0..7]} #{payload['commits'][-1]['message']}" - - http_post 'https://www.notifymyandroid.com/publicapi/notify', - :apikey => data['apikey'], - :application => application, - :event => event, - :description => description, - :url => payload['compare'] - end -end diff --git a/services/ontime.rb b/services/ontime.rb deleted file mode 100644 index 7f1c2c0db..000000000 --- a/services/ontime.rb +++ /dev/null @@ -1,40 +0,0 @@ -class Service::OnTime < Service - string :ontime_url, :api_key - - self.title = 'OnTime' - - def receive_push - if data['ontime_url'].to_s.empty? - raise_config_error "No OnTime URL to connect to." - elsif data['api_key'].to_s.empty? - raise_config_error "No API Key." - end - - # We're just going to send back the entire payload and process it in OnTime. - http.url_prefix = data['ontime_url'] - - # Hash the data, it has to be hexdigest in order to have the same hash value in .NET - hash_data = Digest::SHA256.hexdigest(payload.to_json + data['api_key']) - - resp = http_get "api/version" - version = JSON.parse(resp.body)['data'] - - if (version['major'] == 11 and version['minor'] >= 1) or version['major'] > 11 - result = http_post "api/github", :payload => payload.to_json, :hash_data => hash_data, :source => :github - else - raise_config_error "Unexpected API version. Please update to the latest version of OnTime to use this service." - end - - verify_response(result) - end - - def verify_response(res) - case res.status - when 200..299 - when 403, 401, 422 then raise_config_error("Invalid Credentials") - when 404, 301, 302 then raise_config_error("Invalid URL") - else raise_config_error("HTTP: #{res.status}") - end - end -end - diff --git a/services/pachube.rb b/services/pachube.rb deleted file mode 100644 index 0b0383627..000000000 --- a/services/pachube.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Service::Pachube < Service - string :api_key - string :feed_id - - def receive_push - raise_config_error "Missing api_key" if data['api_key'].to_s.empty? - raise_config_error "Missing feed_id" if data['feed_id'].to_s.empty? - - http_method :put, "https://api.pachube.com/v2/feeds/#{data['feed_id']}.json" do |req| - req.headers['X-PachubeApiKey'] = data['api_key'] - req.body = { - :version => '1.0.0', - :datastreams => [ - { - :id => repo_name, - :current_value => distinct_commits.size - } - ]}.to_json - end - end -end diff --git a/services/presently.rb b/services/presently.rb deleted file mode 100644 index 321955b2c..000000000 --- a/services/presently.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Service::Presently < Service - string :subdomain, :group_name, :username - password :password - - def receive_push - repository = payload['repository']['name'] - - prefix = (data['group_name'].nil? || data['group_name'] == '') ? '' : "b #{data['group_name']} " - - payload['commits'].each do |commit| - status = "#{prefix}[#{repository}] #{commit['author']['name']} - #{commit['message']}" - status = status[0...137] + '...' if status.length > 140 - - paste = "\"Commit #{commit['id']}\":#{commit['url']}\n\n" - paste << "#{commit['message']}\n\n" - - %w(added modified removed).each do |kind| - commit[kind].each do |filename| - paste << "* *#{kind.capitalize}* '#{filename}'\n" - end - end - - http.url_prefix = "https://#{data['subdomain']}.presently.com" - http.basic_auth(data['username'], data['password']) - http_post "/api/twitter/statuses/update.xml", - 'status' => status, - 'source' => 'GitHub', - 'paste_format' => 'textile', - 'paste_text' => paste - end - end -end diff --git a/services/railsbp.rb b/services/railsbp.rb deleted file mode 100644 index ab279b17e..000000000 --- a/services/railsbp.rb +++ /dev/null @@ -1,19 +0,0 @@ -class Service::Railsbp < Service - string :railsbp_url, :token - - def receive_push - http_post railsbp_url, :token => token, :payload => payload.to_json - end - - def railsbp_url - if !(url = data["railsbp_url"].to_s).empty? - url.strip - else - "https://railsbp.com" - end - end - - def token - data['token'].strip - end -end diff --git a/services/rdocinfo.rb b/services/rdocinfo.rb deleted file mode 100644 index c490465d3..000000000 --- a/services/rdocinfo.rb +++ /dev/null @@ -1,7 +0,0 @@ -class Service::RDocInfo < Service - self.title = 'Rdocinfo' - - def receive_push - http_post 'http://rubydoc.info/checkout', :payload => payload.to_json - end -end diff --git a/services/read_the_docs.rb b/services/read_the_docs.rb deleted file mode 100644 index d91dd43e1..000000000 --- a/services/read_the_docs.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Service::ReadTheDocs < Service - def receive_push - http_post "http://readthedocs.org/github", :payload => JSON.generate(payload) - end -end - diff --git a/services/rubyforge.rb b/services/rubyforge.rb deleted file mode 100644 index 5558776ec..000000000 --- a/services/rubyforge.rb +++ /dev/null @@ -1,24 +0,0 @@ -class Service::Rubyforge < Service - string :groupid, :username - password :password - def receive_push - repository = payload['repository']['name'] - branch = ref_name - payload['commits'].each do |commit| - id = commit['id'] - group_id = data['groupid'] - subject = "Commit Notification (#{repository}/#{branch}): #{id}" - body = "`#{commit['message']}`, pushed by #{commit['author']['name']} (#{commit['author']['email']}). View more details for this change at #{commit['url']}." - - post_news(group_id, subject, body) - end - end - - def post_news(group_id, subject, body) - rubyforge.post_news(group_id, subject, body) - end - - def rubyforge - @rubyforge ||= RubyForge.new(data['username'], data['password']) - end -end diff --git a/services/socialcast.rb b/services/socialcast.rb deleted file mode 100644 index bc7dc975a..000000000 --- a/services/socialcast.rb +++ /dev/null @@ -1,39 +0,0 @@ -# encoding: utf-8 -class Service::Socialcast < Service - string :api_domain, :group_id, :username - password :password - def receive_push - repository = payload['repository']['name'] - group_id = (data['group_id'].nil? || data['group_id'] == '') ? '' : data['group_id'] - kind_symbol = Hash["added" => "+", "modified" => "Ξ”", "removed" => "-"] - s_if_plural = (payload['commits'].length > 1) ? 's' : '' - title = "#{payload['commits'].length} commit#{s_if_plural} pushed to GitHub repo [#{repository}]" - message = "" - - payload['commits'].each_with_index do |commit, i| - timestamp = Date.parse(commit['timestamp']) - heading = "√#{i+1} by #{commit['author']['name']} at #{timestamp}" - message << "#{heading}\n" - heading.length.times do - message << "-" - end - message << "\n#{commit['url']}\n#{commit['message']}\n" - - %w(added modified removed).each do |kind| - commit[kind].each do |filename| - message << "#{kind_symbol[kind]} '#{filename}'\n" - end - end - - message << "\n" - end - - http.ssl[:verify] = false - http.basic_auth(data['username'], data['password']) - http_post "https://#{data['api_domain']}/api/messages.xml", - 'message[title]' => title, - 'message[body]' => message, - 'message[group_id]' => group_id - end -end - diff --git a/services/sourcemint.rb b/services/sourcemint.rb deleted file mode 100644 index 36f058d27..000000000 --- a/services/sourcemint.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Service::Sourcemint < Service - def receive_push - http_post 'http://api.sourcemint.com/actions/post-commit', - :payload => JSON.generate(payload) - end -end diff --git a/services/splendid_bacon.rb b/services/splendid_bacon.rb deleted file mode 100644 index e5d7177d1..000000000 --- a/services/splendid_bacon.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Service::SplendidBacon < Service - string :project_id, :token - - def receive_push - token = data['token'] - project_id = data['project_id'] - http.url_prefix = 'https://splendidbacon.com' - http_post "/api/v1/projects/#{project_id}/github" do |req| - req.params[:token] = token - req.body = {:payload => payload.to_json} - end - end -end diff --git a/services/stackmob.rb b/services/stackmob.rb deleted file mode 100644 index a822dcc4a..000000000 --- a/services/stackmob.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Service::Stackmob < Service - string :token - - TOKEN_KEY = 'token' - BASE_URL = "https://deploy.stackmob.com/callback" - - def receive_push - token = data[TOKEN_KEY] - raise_config_error "no token" if token.empty? - - http.url_prefix = BASE_URL - http.headers['Content-Type'] = 'application/json' - - http_post token, payload.to_json - end - -end - - - diff --git a/services/teamcity.rb b/services/teamcity.rb deleted file mode 100644 index 308135cbe..000000000 --- a/services/teamcity.rb +++ /dev/null @@ -1,22 +0,0 @@ -class Service::TeamCity < Service - string :base_url, :build_type_id, :username - password :password - - def receive_push - # :( - http.ssl[:verify] = false - - http.url_prefix = data['base_url'] - http.basic_auth data['username'], data['password'] - res = http_get "httpAuth/action.html", :add2Queue => data['build_type_id'] - case res.status - when 200..299 - when 403, 401, 422 then raise_config_error("Invalid credentials") - when 404, 301, 302 then raise_config_error("Invalid TeamCity URL") - else raise_config_error("HTTP: #{res.status}") - end - rescue SocketError => e - raise_config_error "Invalid TeamCity host name" if e.to_s =~ /getaddrinfo: Name or service not known/ - raise - end -end diff --git a/services/test_pilot.rb b/services/test_pilot.rb deleted file mode 100644 index d145e8a86..000000000 --- a/services/test_pilot.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Service::TestPilot < Service - string :token - - def receive_push - http.ssl[:verify] = false - http.params.merge!(authentication_param) - http_post test_pilot_url, :payload => payload.to_json - end - - def test_pilot_url - "http://testpilot.me/callbacks/github" - end - - def token - data['token'].to_s.strip - end - - def authentication_param - if token.empty? - raise_config_error "Needs a token" - end - - {:token => token} - end -end - diff --git a/services/trajectory.rb b/services/trajectory.rb deleted file mode 100644 index 2e201f9c4..000000000 --- a/services/trajectory.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Service::Trajectory < Service - string :api_key - - def receive_push - raise_config_error "Missing 'api_key'" if data['api_key'].to_s == '' - - http.headers['content-type'] = 'application/json' - - res = http_post "https://www.apptrajectory.com/api/payloads?api_key=#{data['api_key'].to_s}", JSON.generate(payload) - - if res.status < 200 || res.status > 299 - raise_config_error - end - end -end diff --git a/services/travis.rb b/services/travis.rb deleted file mode 100644 index c96e196e2..000000000 --- a/services/travis.rb +++ /dev/null @@ -1,48 +0,0 @@ -class Service::Travis < Service - string :domain, :user, :token - - def receive_push - http.ssl[:verify] = false - http.basic_auth user, token - http_post travis_url, :payload => payload.to_json - end - - def travis_url - "#{scheme}://#{domain}/builds" - end - - def user - if data['user'].to_s == '' - payload['repository']['owner']['name'] - else - data['user'] - end.strip - end - - def token - data['token'].strip - end - - def scheme - domain_parts.size == 1 ? 'http' : domain_parts.first - end - - def domain - domain_parts.last - end - - protected - - def full_domain - if data['domain'].to_s == '' - 'http://travis-ci.org' - else - data['domain'] - end.strip - end - - def domain_parts - @domain_parts ||= full_domain.split('://') - end -end - diff --git a/services/twitter.rb b/services/twitter.rb deleted file mode 100644 index b0db47663..000000000 --- a/services/twitter.rb +++ /dev/null @@ -1,51 +0,0 @@ -class Service::Twitter < Service - string :token, :secret - boolean :digest - - def receive_push - return unless payload['commits'] - - statuses = [] - repository = payload['repository']['name'] - - if data['digest'] == '1' - commit = payload['commits'][-1] - author = commit['author'] || {} - tiny_url = shorten_url("#{payload['repository']['url']}/commits/#{ref_name}") - status = "[#{repository}] #{tiny_url} #{author['name']} - #{payload['commits'].length} commits" - status.length >= 140 ? statuses << status[0..136] + '...' : statuses << status - else - payload['commits'].each do |commit| - author = commit['author'] || {} - tiny_url = shorten_url(commit['url']) - status = "[#{repository}] #{tiny_url} #{author['name']} - #{commit['message']}" - status.length >= 140 ? statuses << status[0..136] + '...' : statuses << status - end - end - - statuses.each do |status| - post(status) - end - end - - def post(status) - params = { 'status' => status, 'source' => 'github' } - - access_token = ::OAuth::AccessToken.new(consumer, data['token'], data['secret']) - consumer.request(:post, "/1/statuses/update.json", - access_token, { :scheme => :query_string }, params) - end - - def consumer_key - secrets['twitter']['key'] - end - - def consumer_secret - secrets['twitter']['secret'] - end - - def consumer - @consumer ||= ::OAuth::Consumer.new(consumer_key, consumer_secret, - {:site => "http://api.twitter.com"}) - end -end diff --git a/services/web.rb b/services/web.rb deleted file mode 100644 index 1e32cad5f..000000000 --- a/services/web.rb +++ /dev/null @@ -1,63 +0,0 @@ -class Service::Web < Service - HMAC_DIGEST = OpenSSL::Digest::Digest.new('sha1') - - string :url, - # adds a X-Hub-Signature of the body content - # X-Hub-Signature: sha1=.... - :secret, - - # old hooks send form params ?payload=JSON(...) - # new hooks should set content_type == 'json' - :content_type - - boolean :insecure_ssl # :( - - def receive_event - url = data['url'].to_s - url.gsub! /\s/, '' - - if url.empty? - raise_config_error "Invalid URL: #{url.inspect}" - end - - if url !~ /^https?\:\/\// - url = "http://#{url}" - end - - # set this so that basic auth is added, - # and GET params are added to the POST body - http.url_prefix = url - http.headers['X-GitHub-Event'] = event.to_s - - if data['insecure_ssl'].to_i == 1 - http.ssl[:verify] = false - end - - body = if data['content_type'] == 'json' - http.headers['content-type'] = 'application/json' - JSON.generate(payload) - else - http.headers['content-type'] = 'application/x-www-form-urlencoded' - Faraday::Utils.build_nested_query( - http.params.merge(:payload => JSON.generate(payload))) - end - - if !(secret = data['secret'].to_s).empty? - http.headers['X-Hub-Signature'] = - 'sha1='+OpenSSL::HMAC.hexdigest(HMAC_DIGEST, secret, body) - end - - http_post url, body - rescue Addressable::URI::InvalidURIError, Errno::EHOSTUNREACH - raise_config_error $!.to_s - rescue SocketError - if $!.to_s =~ /getaddrinfo:/ - raise_config_error "Invalid host name." - else - raise - end - rescue EOFError - raise_config_error "Invalid server response. Make sure the URL uses the correct protocol." - end -end - diff --git a/services/yammer.rb b/services/yammer.rb deleted file mode 100644 index 941fcb627..000000000 --- a/services/yammer.rb +++ /dev/null @@ -1,56 +0,0 @@ -class Service::Yammer < Service - string :group_id, :consumer_key, :consumer_secret, - :access_token, :access_secret - boolean :digest - - def receive_push - statuses = [ ] - repository = payload['repository']['name'] - - if data['digest'] == '1' - commit = payload['commits'][-1] - tiny_url = shorten_url("#{payload['repository']['url']}/commits/#{ref_name}") - statuses << "@#{commit['author']['name']} pushed #{payload['commits'].length}. #{tiny_url} \##{repository}" - else - payload['commits'].each do |commit| - tiny_url = shorten_url(commit['url']) - statuses << "#{commit['message']} (committer: @#{commit['author']['name']}) #{tiny_url} \##{repository}" - end - end - - statuses.each do |status| - params = { :body => status } - params['group_id'] = data['group_id'] unless data['group_id'].to_s.empty? - begin - send_message params - rescue - if $!.to_s =~ /authentication failed/i - raise_config_error "Invalid username or password" - else - raise - end - end - end - end - - def send_message(params) - yammer.message(:post, params) - rescue RuntimeError => boom - if boom.to_s =~ /Bad Request/i - raise_config_error boom.to_s - else - raise - end - end - - def yammer - @yammer ||= ::Yammer::Client.new \ - :consumer => { - :key => data['consumer_key'], - :secret => data['consumer_secret']}, - :access => { - :token => data['access_token'], - :secret => data['access_secret']} - end -end - diff --git a/services/you_track.rb b/services/you_track.rb deleted file mode 100644 index a0a333d00..000000000 --- a/services/you_track.rb +++ /dev/null @@ -1,83 +0,0 @@ -class Service::YouTrack < Service - string :base_url, :committers, :username - password :password - - def receive_push - http.ssl[:verify] = false - http.url_prefix = data['base_url'] - payload['commits'].each { |c| process_commit(c) } - end - - def login - @logged_in ||= begin - res = http_post 'rest/user/login' do |req| - req.params.update \ - :login => data['username'], - :password => data['password'] - req.headers['Content-Length'] = '0' - end - verify_response(res) - - http.headers['Cookie'] = res.headers['set-cookie'] - http.headers['Cache-Control'] = 'no-cache' - true - end - end - - def process_commit(commit) - author = nil - commit["message"].split("\n").each{ |commit_line| - issue_id = commit_line[/( |^)#(\w+-\d+) /, 2] - next if issue_id.nil? - - # lazily load author - author ||= find_user_by_email(commit["author"]["email"]) - return if author.nil? - - command = commit_line[/( |^)#\w+-\d+ (.+)/, 2].strip - command = "Fixed" if command.nil? - commentString = "Commit made by '''" + commit["author"]["name"] + "''' on ''" + commit["timestamp"] + "''\n" + commit["url"] + "\n\n{quote}" + commit["message"].to_s + "{quote}" - execute_command(author, issue_id, command, commentString) - } - end - - def find_user_by_email(email) - login - counter = 0 - found_user = nil - while true - body = "" - res = http_get "rest/admin/user", :q => email, :group => data['committers'], :start => counter - verify_response(res) - xml_body = REXML::Document.new(res.body) - xml_body.root.each_element do |user_ref| - res = http_get "rest/admin/user/#{user_ref.attributes['login']}" - verify_response(res) - if REXML::Document.new(res.body).root.attributes["email"].upcase == email.upcase - return if !found_user.nil? - found_user = user_ref.attributes["login"] - end - end - return found_user if xml_body.root.elements.size < 10 - counter += 10 - end - end - - def execute_command(author, issue_id, command, commentString) - res = http_post "rest/issue/#{issue_id}/execute" do |req| - req.params[:command] = command - req.params[:comment] = commentString - req.params[:runAs] = author - end - verify_response(res) - end - - def verify_response(res) - case res.status - when 200..299 - when 403, 401, 422 then raise_config_error("Invalid Credentials") - when 404, 301, 302 then raise_config_error("Invalid YouTrack URL") - else raise_config_error("HTTP: #{res.status}") - end - end -end diff --git a/test/active_collab_test.rb b/test/active_collab_test.rb index 04f9225d4..3dc016096 100644 --- a/test/active_collab_test.rb +++ b/test/active_collab_test.rb @@ -7,8 +7,8 @@ def setup def test_push @stubs.post "/foo" do |env| - query = Rack::Utils.parse_nested_query(env[:url].query) - body = Rack::Utils.parse_nested_query(env[:body]) + query = Faraday::Utils.parse_nested_query(env[:url].query) + body = Faraday::Utils.parse_nested_query(env[:body]) assert_equal '2', body['discussion']['milestone_id'] assert_equal '3', body['discussion']['parent_id'] assert_equal 'activecollab.com', env[:url].host @@ -38,6 +38,3 @@ def service(*args) super Service::ActiveCollab, *args end end - - - diff --git a/test/agilezen_test.rb b/test/agilezen_test.rb deleted file mode 100644 index 2b53617f6..000000000 --- a/test/agilezen_test.rb +++ /dev/null @@ -1,84 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class AgileZenTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_unspecified_branch - payload = {'answer' => 42, 'ref' => 'refs/heads/master'} - @stubs.post '/api/v1/projects/123/changesets/github' do |env| - assert_equal payload.to_json, env[:body] - assert_equal 'test_api_key', env[:request_headers]['X-Zen-ApiKey'] - assert_equal 'application/json', env[:request_headers]['Content-Type'] - [200, {}, ''] - end - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123'}, payload) - svc.receive_push - @stubs.verify_stubbed_calls - end - - def test_matching_branch - payload = {"ref" => "refs/heads/foo"} - @stubs.post("/api/v1/projects/123/changesets/github") { |e| [200, {}, ''] } - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123', 'branches' => 'foo'}, payload) - svc.receive_push - @stubs.verify_stubbed_calls - end - - def test_unmatching_branch - payload = {"ref" => "refs/heads/bar"} - @stubs.post("/api/v1/projects/123/changesets/github") { |e| [200, {}, ''] } - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123', 'branches' => 'foo'}, payload) - svc.receive_push - - # Test that no post fired - begin - @stubs.verify_stubbed_calls - rescue RuntimeError - else - assert_true false - end - end - - def test_matching_branch_of_many - payload = {"ref" => "refs/heads/foo"} - @stubs.post("/api/v1/projects/123/changesets/github") { |e| [200, {}, ''] } - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123', 'branches' => 'baz foo'}, payload) - svc.receive_push - @stubs.verify_stubbed_calls - end - - def test_unmatching_branch_of_many - payload = {"ref" => "refs/heads/bar"} - @stubs.post("/api/v1/projects/123/changesets/github") { |e| [200, {}, ''] } - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123', 'branches' => 'baz foo'}, payload) - svc.receive_push - - # Test that no post fired - begin - @stubs.verify_stubbed_calls - rescue RuntimeError - else - assert_true false - end - end - - def test_matching_tag - payload = {"ref" => "refs/tags/foo"} - @stubs.post("/api/v1/projects/123/changesets/github") { |e| [200, {}, ''] } - - svc = service({'api_key' => 'test_api_key', 'project_id' => '123', 'branches' => 'foo'}, payload) - svc.receive_push - @stubs.verify_stubbed_calls - end - - def service(*args) - super Service::AgileZen, *args - end -end diff --git a/test/amazon_sns_test.rb b/test/amazon_sns_test.rb new file mode 100644 index 000000000..5968003f6 --- /dev/null +++ b/test/amazon_sns_test.rb @@ -0,0 +1,95 @@ +require File.expand_path('../helper', __FILE__) + +class Hash + def except!(*keys) + keys.each { |key| delete(key) } + self + end +end + +class AmazonSNSTest < Service::TestCase + + # SNS maximum message size is 256 kilobytes. + SNS_MAX_MESSAGE_SIZE = 256 * 1024 + + # Use completely locked down IAM resource. + def data + { + 'aws_key' => 'AKIAJV3OTFPCKNH53IBQ', + 'aws_secret' => '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', + 'sns_topic' => 'arn:aws:sns:us-east-1:718656560584:github-service-hook-test', + 'sns_region' => 'us-east-1' + } + end + + def payload + { + "test" => "true" + } + end + + def large_payload + { + "test" => 0.to_s * (SNS_MAX_MESSAGE_SIZE + 1) + } + end + + def xtest_event + svc = service :push, data, payload + sns = svc.receive_event + + assert_equal data['aws_key'], svc.data['aws_key'] + assert_equal data['aws_secret'], svc.data['aws_secret'] + assert_equal data['sns_topic'], svc.data['sns_topic'] + assert_equal data['sns_region'], svc.data['sns_region'] + end + + def verify_requires(svc) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def verify_nothing_raised(svc) + assert_nothing_raised do + svc.receive_event + end + end + + def test_requires_aws_key + verify_requires(service :push, data.except!('aws_key'), payload) + end + + def test_requires_aws_secret + verify_requires(service :push, data.except!('aws_secret'), payload) + end + + def test_requires_sns_topic + verify_requires(service :push, data.except!('sns_topic'), payload) + end + + def test_requires_sns_topic + verify_requires(service :push, data.except!('sns_topic'), payload) + end + + def test_defaults_sns_region + svc = service :push, data.except!('sns_region'), payload + svc.validate_data + + assert_equal svc.data['sns_region'], data['sns_region'] + end + + def test_publish_to_sns + skip 'aws_key is outdated, and this test will fail. Consider updating/refactoring out aws credentials to re-enable this test' + verify_nothing_raised(service :push, data, payload) + end + + def test_payload_exceeds_256K + skip 'aws_key is outdated, and this test will fail. Consider updating/refactoring out aws credentials to re-enable this test' + verify_nothing_raised(service :push, data, large_payload) + end + + def service(*args) + super Service::AmazonSNS, *args + end +end diff --git a/test/amqp_test.rb b/test/amqp_test.rb deleted file mode 100644 index fe79552e2..000000000 --- a/test/amqp_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class AMQPTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - data = { 'host' => 'host', 'exchange' => 'exchange' } - payload = { - 'repository' => { - 'owner' => {'name' => 'owner'}, - 'name' => 'owner/repo' - }, - 'ref' => 'refs/heads/ref', - 'commits' => [{ - 'author' => {'email' => 'author@email.com'}, - 'sha' => 'sha' - }] - } - - svc = service :push, data, payload - svc.amqp_connection = connection_stub - svc.amqp_exchange = exchange_stub - svc.receive - - routing_key = "github.push.owner.owner/repo.ref" - - assert msg = svc.amqp_exchange.messages.shift - assert_equal({ - '_meta' => { - 'routing_key' => routing_key, - 'exchange' => 'exchange' - }, - 'payload' => payload - }, msg[:body]) - assert_equal routing_key, msg[:key] - - routing_key = "github.commit.owner.owner/repo.ref.author@email.com" - assert msg = svc.amqp_exchange.messages.shift - assert_equal({ - '_meta' => { - 'routing_key' => routing_key, - 'exchange' => 'exchange' - }, - 'payload' => payload['commits'][0] - }, msg[:body]) - assert_equal routing_key, msg[:key] - - assert_nil svc.amqp_exchange.messages.shift - end - - def test_requires_data_host - svc = service :push, {}, 'payload' - assert_raise Service::ConfigurationError do - svc.receive - end - end - - def test_requires_data_exchange - svc = service :push, {'data' => 'a'}, 'payload' - assert_raise Service::ConfigurationError do - svc.receive - end - end - - def connection_stub - conn = Object.new - def conn.close() end - conn - end - - def exchange_stub - FakeExchange.new - end - - def service(*args) - super Service::AMQP, *args - end - - class FakeExchange - attr_reader :messages - - def initialize - @messages = [] - end - - def publish(body, options) - @messages << options.update(:body => JSON.parse(body)) - end - end -end - - diff --git a/test/apiary_test.rb b/test/apiary_test.rb new file mode 100644 index 000000000..68bce9377 --- /dev/null +++ b/test/apiary_test.rb @@ -0,0 +1,28 @@ +require File.expand_path('../helper', __FILE__) + +class ApiaryTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + options = { + 'branch' => 'master', + 'domain' => 'testvanity', + 'apiary_address' => nil } + @svc = service(options, nil) + end + + def test_push + @stubs.post "/github/service-hook" do |env| + body = Faraday::Utils.parse_query(env[:body]) + assert_equal 'api.apiary.io', env[:url].host + assert_equal @svc.payload, JSON.parse(body['payload']) + assert_equal @svc.branch, body['branch'] + assert_equal @svc.domain, body['vanity'] + [200, {}, ''] + end + @svc.receive_event + end + + def service(*args) + super Service::Apiary, *args + end +end diff --git a/test/appharbor_test.rb b/test/appharbor_test.rb index 8bc205c82..a8e777ed0 100644 --- a/test/appharbor_test.rb +++ b/test/appharbor_test.rb @@ -21,7 +21,7 @@ def service(*args) def test_push(application_slugs, token) application_slugs.split(",").each do |slug| - @stubs.post "/application/#{slug}/build" do |env| + @stubs.post "/applications/#{slug}/builds" do |env| verify_appharbor_payload(token, env) end end @@ -33,14 +33,14 @@ def test_push(application_slugs, token) end def verify_appharbor_payload(token, env) - assert_equal token, env[:params]['authorization'] + assert_equal "BEARER #{token}", env[:request_headers]['authorization'] assert_equal 'application/json', env[:request_headers]['accept'] branches = JSON.parse(env[:body])['branches'] assert_equal 1, branches.size branch = branches[payload['ref'].sub(/\Arefs\/heads\//, '')] - assert_not_nil branch + refute_nil branch assert_equal payload['after'], branch['commit_id'] assert_equal payload['commits'].select{|c| c['id'] == payload['after']}.first['message'], branch['commit_message'] end diff --git a/test/apropos_test.rb b/test/apropos_test.rb new file mode 100644 index 000000000..56e9cc22a --- /dev/null +++ b/test/apropos_test.rb @@ -0,0 +1,27 @@ +require File.expand_path('../helper', __FILE__) + +class WebTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/api/v1/githook/abcdefg01234" do |env| + assert_equal 'application/json', env[:request_headers]['Content-Type'] + assert_equal 'www.apropos.io', env[:url].host + assert_equal payload, JSON.parse(env[:body]) + [200, {}, '{"message":"OK"}'] + end + + svc = service({ + 'project_id' => 'abcdefg01234', + 'content_type' => 'json' + }, payload) + svc.receive_push + end + + def service(*args) + super Service::Apropos, *args + end + +end diff --git a/test/asana_test.rb b/test/asana_test.rb new file mode 100644 index 000000000..520aae4de --- /dev/null +++ b/test/asana_test.rb @@ -0,0 +1,147 @@ +require File.expand_path('../helper', __FILE__) + +class AsanaTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def decode_body(body) + Hash[URI.decode_www_form(body)] + end + + def test_push + + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + body = decode_body(env[:body]) + assert_match /rtomayko pushed to branch master of mojombo\/grit/, body["text"] + assert_match /#1234/, body["text"] + assert_match /Basic MDAwMDo=/, env[:request_headers][:authorization] + [200, {}, ''] + end + + @stubs.post "/api/1.0/tasks/1235/stories" do |env| + body = decode_body(env[:body]) + assert_match /rtomayko pushed to branch master of mojombo\/grit/, body["text"] + assert_match /#1235/, body["text"] + assert_match /Basic MDAwMDo=/, env[:request_headers][:authorization] + [200, {}, ''] + end + + svc = service( + {'auth_token' => '0000'}, + modified_payload) + svc.receive_push + end + + def test_restricted_comment_commit_push + + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + body = decode_body(env[:body]) + assert_match /rtomayko pushed to branch master of mojombo\/grit/, body["text"] + refute_match /stub git call for Grit#heads test f:15 Case#1234/, body["text"] + assert_match /add more comments about #1235 and #1234 throughout/, body["text"] + assert_match /#1234/, body["text"] + assert_match /Basic MDAwMDo=/, env[:request_headers][:authorization] + [200, {}, ''] + end + + @stubs.post "/api/1.0/tasks/1235/stories" do |env| + body = decode_body(env[:body]) + assert_match /rtomayko pushed to branch master of mojombo\/grit/, body["text"] + refute_match /#1234 clean up heads test f:2hrs #1235/, body["text"] + assert_match /add more comments about #1235 and #1234 throughout/, body["text"] + assert_match /#1235/, body["text"] + assert_match /Basic MDAwMDo=/, env[:request_headers][:authorization] + [200, {}, ''] + end + + svc = service( + {'auth_token' => '0000',"restrict_to_last_commit" => "1"}, + modified_payload) + svc.receive_push + end + + def test_restricted_branch_commit_push + + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + body = decode_body(env[:body]) + refute_match /stub git call for Grit#heads test f:15 Case#1234/, body["text"] + [200, {}, ''] + end + + @stubs.post "/api/1.0/tasks/1235/stories" do |env| + body = decode_body(env[:body]) + refute_match /#1234 clean up heads test f:2hrs #1235/, body["text"] + [200, {}, ''] + end + + svc = service( + {'auth_token' => '0000',"restrict_to_branch" => "foo,bar"}, + modified_payload) + svc.receive_push + end + + def test_merge_pull_request_payload + @stubs.post "/api/1.0/tasks/42/stories" do |env| + [400, {}, ''] # Asana responds with 400 for unknown tasks + end + + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + body = decode_body(env[:body]) + assert_match /#1234/, body["text"] + [200, {}, ''] + end + + svc = service({'auth_token' => '0000'}, merge_payload) + assert_nothing_raised { svc.receive_push } + end + + def test_error_response + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + [401, {"Content-Type" => "application/json; charset=UTF-8"}, '{"errors":[{"message":"Not Authorized"}]}'] + end + + svc = service( {'auth_token' => 'bad-token'}, modified_payload) + + begin + svc.receive_push + rescue StandardError => e + assert_equal Service::ConfigurationError, e.class + assert_equal "Not Authorized", e.message + end + end + + def test_asana_exception + @stubs.post "/api/1.0/tasks/1234/stories" do |env| + [500, {}, 'Boom!'] + end + + svc = service( {'auth_token' => '0000'}, modified_payload) + + begin + svc.receive_push + rescue StandardError => e + assert_equal Service::ConfigurationError, e.class + assert_equal "Unexpected Error", e.message + end + end + + def service(*args) + super Service::Asana, *args + end + + def modified_payload + pay = payload + pay['commits'][0]['message'] = "stub git call for Grit#heads test f:15 Case#1234" + pay['commits'][1]['message'] = "#1234 clean up heads test f:2hrs #1235" + pay['commits'][2]['message'] = "add more comments about #1235 and #1234 throughout" + pay + end + + def merge_payload + pay = payload + pay['commits'][0]['message'] = "Merge pull request #42. Fixes Asana task #1234." + pay + end + + end diff --git a/test/auto_deploy_test.rb b/test/auto_deploy_test.rb new file mode 100644 index 000000000..fc84fb076 --- /dev/null +++ b/test/auto_deploy_test.rb @@ -0,0 +1,210 @@ +require File.expand_path('../helper', __FILE__) + +class AutoDeployTest < Service::TestCase + include Service::HttpTestMethods + + def auto_deploy_on_push_service_data + { + 'github_token' => github_token, + 'environments' => 'production', + 'deploy_on_status' => '0' + } + end + + def auto_deploy_on_status_service_data(options = { }) + auto_deploy_on_push_service_data.merge options.merge('deploy_on_status' => '1') + end + + def auto_deploy_on_push_service + service(:push, auto_deploy_on_push_service_data, push_payload) + end + + def auto_deploy_on_status_service(options = { }) + service(:status, auto_deploy_on_status_service_data(options), status_payload) + end + + def test_unsupported_deployment_events + exception = assert_raises(Service::ConfigurationError) do + service(:deployment, auto_deploy_on_push_service_data, deployment_payload).receive_event + end + + message = "The deployment event is currently unsupported." + assert_equal message, exception.message + end + + def test_push_deployment_configured_properly + stub_github_repo_deployment_access + services_sha = Service.current_sha[0..7] + + github_post_body = { + "ref" => "a47fd41f", + "payload" => {"hi"=>"haters"}, + "environment" => "production", + "description" => "Auto-Deployed on push by GitHub Services@#{services_sha} for rtomayko - master@a47fd41f", + "required_contexts" => [ ], + } + + github_deployment_path = "/repos/mojombo/grit/deployments" + + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + auto_deploy_on_push_service.receive_event + @stubs.verify_stubbed_calls + end + + def test_push_deployment_configured_for_status + stub_github_repo_deployment_access + + # successful push to default branch but configured for status deployments + service(:push, auto_deploy_on_status_service_data, status_payload).receive_event + @stubs.verify_stubbed_calls + end + + def test_status_deployment_configured_properly + stub_github_repo_deployment_access + services_sha = Service.current_sha[0..7] + + github_post_body = { + "ref" => "7b80eb10", + "payload" => {"hi"=>"haters"}, + "environment" => "production", + "description" => "Auto-Deployed on status by GitHub Services@#{services_sha} for rtomayko - master@7b80eb10", + "required_contexts" => [ ], + } + + github_deployment_path = "/repos/mojombo/grit/deployments" + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + auto_deploy_on_status_service.receive_event + @stubs.verify_stubbed_calls + end + + def test_status_deployment_configured_properly_on_enterprise + services_sha = Service.current_sha[0..7] + + github_post_body = { + "ref" => "7b80eb10", + "payload" => {"hi"=>"haters"}, + "environment" => "production", + "description" => "Auto-Deployed on status by GitHub Services@#{services_sha} for rtomayko - master@7b80eb10", + "required_contexts" => [ ], + } + + github_deployment_path = "/repos/mojombo/grit/deployments" + @stubs.post github_deployment_path do |env| + assert_equal 'enterprise.myorg.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + @stubs.get "/user" do |env| + assert_equal 'enterprise.myorg.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + [200, {}, ''] + end + + deployment_history = [{'environment' => 'staging', 'id' => 42}, + {'environment' => 'production', 'id' => 43, 'payload' => {'hi' => 'haters'}}].to_json + + @stubs.get "/repos/mojombo/grit/deployments" do |env| + assert_equal 'enterprise.myorg.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + headers = {"X-OAuth-Scopes" => "repo,deployment" } + [200, headers, deployment_history] + end + + auto_deploy_on_status_service('github_api_url' => 'https://enterprise.myorg.com').receive_event + @stubs.verify_stubbed_calls + end + + def test_status_deployment_configured_with_failure_status + stub_github_repo_deployment_access + + # don't do anything on failed states + failed_status_payload = status_payload.merge('state' => 'failure') + service(:status, auto_deploy_on_status_service_data, failed_status_payload).receive_event + @stubs.verify_stubbed_calls + end + + def test_status_deployment_configured_for_push + stub_github_repo_deployment_access + + # successful commit status but configured for push + service(:status, auto_deploy_on_push_service_data, status_payload).receive_event + @stubs.verify_stubbed_calls + end + + def test_deployment_with_bad_github_user_credentials + stub_github_user(404) + + exception = assert_raises(Service::ConfigurationError) do + auto_deploy_on_push_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to access GitHub with the provided token." + assert_equal message, exception.message + end + + def test_deployment_without_access_to_github_repo_deployments + stub_github_repo_deployment_access(404) + + exception = assert_raises(Service::ConfigurationError) do + auto_deploy_on_push_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to access the mojombo/grit repository's deployments on GitHub with the provided token." + assert_equal message, exception.message + end + + def test_slashed_payload_ref + payload = { 'ref' => 'refs/heads/slash/test' } + service = Service::AutoDeploy.new(:push, {}, payload) + assert_equal 'slash/test', service.payload_ref + end + + def service_class + Service::AutoDeploy + end + + def stub_github_user(code = 200) + @stubs.get "/user" do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + [code, {}, ''] + end + end + + def stub_github_repo_deployment_access(code = 200, scopes = "repo:deployment, user") + stub_github_user + deployment_history = [{'environment' => 'staging', 'id' => 42}, + {'environment' => 'production', 'id' => 43, 'payload' => {'hi' => 'haters'}}].to_json + + @stubs.get "/repos/mojombo/grit/deployments" do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + headers = {"X-OAuth-Scopes" => scopes } + [code, headers, deployment_history] + end + end + + def github_token + @github_token ||= SecureRandom.hex(24) + end +end diff --git a/test/aws_code_deploy_test.rb b/test/aws_code_deploy_test.rb new file mode 100644 index 000000000..411c5605c --- /dev/null +++ b/test/aws_code_deploy_test.rb @@ -0,0 +1,134 @@ +require File.expand_path('../helper', __FILE__) +ENV['CODE_DEPLOY_STUB_RESPONSES'] = 'true' + +class AwsCodeDeployDeploymentTest < Service::TestCase + include Service::HttpTestMethods + + def setup + super + end + + def test_deployment_group_sent + response = aws_service.receive_event + assert_equal sample_data['deployment_group'], response.context[:deployment_group] + end + + def test_environmental_deployment_group_sent + svc = Service::AwsCodeDeploy.new(:deployment, sample_data, environmental_payload) + + response = svc.receive_event + deployment_group = code_deploy_deployment_environments['staging']['deployment_group'] + assert_equal deployment_group, response.context[:deployment_group] + end + + def test_application_name_sent + svc = Service::AwsCodeDeploy.new(:deployment, sample_data, environmental_payload) + response = svc.receive_event + application_name = code_deploy_deployment_environments['staging']['application_name'] + assert_equal application_name, response.context[:application_name] + end + + def test_environmental_application_name_sent + svc = Service::AwsCodeDeploy.new(:deployment, sample_data, environmental_payload) + response = svc.receive_event + application_name = code_deploy_deployment_environments['staging']['application_name'] + assert_equal application_name, response.context[:application_name] + end + + def test_application_name_missing + svc = aws_service(sample_data.except('application_name')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_access_key_id_configured + config = aws_service.code_deploy_client.config + assert_equal sample_data['aws_access_key_id'], config.access_key_id + end + + def test_aws_access_key_id_missing + svc = aws_service(sample_data.except('aws_access_key_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_secret_access_key_configured + config = aws_service.code_deploy_client.config + assert_equal sample_data['aws_secret_access_key'], config.secret_access_key + end + + def test_aws_secret_access_key_missing + svc = aws_service(sample_data.except('aws_secret_access_key')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_github_deployment_status_callbacks + github_post_body = { + "state" => "success", + "target_url" => "https://console.aws.amazon.com/codedeploy/home?region=us-east-1#/deployments", + "description" => "Deployment 721 Accepted by Amazon. (github-services@#{Service.current_sha[0..7]})" + } + + github_deployment_path = "/repos/atmos/my-robot/deployments/721/statuses" + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + custom_sample_data = sample_data.merge('github_token' => 'secret') + svc = service(:deployment, custom_sample_data, environmental_payload) + response = svc.receive_event + application_name = code_deploy_deployment_environments['staging']['application_name'] + assert_equal application_name, response.context[:application_name] + + @stubs.verify_stubbed_calls + end + + def aws_service(data = sample_data, payload = sample_payload) + Service::AwsCodeDeploy.new(:deployment, data, payload) + end + + def service_class + Service::AwsCodeDeploy + end + + def sample_data + { + 'aws_access_key_id' => 'AKIA1234567890123456', + 'aws_secret_access_key' => '0123456789+0123456789+0123456789+0123456', + 'application_name' => 'testapp', + 'deployment_group_name' => 'production' + } + end + + def code_deploy_deployment_environments + { + 'staging' => { + }, + 'production' => { + } + } + end + + def environmental_payload + custom_payload = { + 'environment' => 'staging', + 'payload' => { + 'config' => { + 'aws_code_deploy' => code_deploy_deployment_environments + } + } + } + Service::DeploymentHelpers.sample_deployment_payload.merge(custom_payload) + end + + def sample_payload(branch_name = 'default-branch') + Service::DeploymentHelpers.sample_deployment_payload.merge('ref' => "refs/heads/#{branch_name}") + end +end diff --git a/test/aws_ops_works_deployment_test.rb b/test/aws_ops_works_deployment_test.rb new file mode 100644 index 000000000..c20c4d348 --- /dev/null +++ b/test/aws_ops_works_deployment_test.rb @@ -0,0 +1,148 @@ +require File.expand_path('../helper', __FILE__) + +class AwsOpsWorksDeploymentTest < Service::TestCase + include Service::HttpTestMethods + + def setup + super + AWS.stub! + end + + def test_stack_id_sent + response = aws_service.receive_event + assert_equal sample_data['stack_id'], response.request_options[:stack_id] + end + + def test_environmental_stack_id_sent + svc = Service::AwsOpsWorks.new(:deployment, sample_data, environmental_payload) + + response = svc.receive_event + stack_id = opsworks_deployment_environments['staging']['stack_id'] + assert_equal stack_id, response.request_options[:stack_id] + end + + def test_stack_id_missing + svc = aws_service(sample_data.except('stack_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_app_id_sent + response = aws_service.receive_event + assert_equal sample_data['app_id'], response.request_options[:app_id] + end + + def test_environmental_app_id_sent + svc = Service::AwsOpsWorks.new(:deployment, sample_data, environmental_payload) + response = svc.receive_event + app_id = opsworks_deployment_environments['staging']['app_id'] + assert_equal app_id, response.request_options[:app_id] + end + + def test_app_id_missing + svc = aws_service(sample_data.except('app_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_access_key_id_configured + config = aws_service.ops_works_client.config + assert_equal sample_data['aws_access_key_id'], config.access_key_id + end + + def test_aws_access_key_id_missing + svc = aws_service(sample_data.except('aws_access_key_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_secret_access_key_configured + config = aws_service.ops_works_client.config + assert_equal sample_data['aws_secret_access_key'], config.secret_access_key + end + + def test_aws_secret_access_key_missing + svc = aws_service(sample_data.except('aws_secret_access_key')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_github_deployment_status_callbacks + github_post_body = { + "state" => "success", + "target_url" => "https://console.aws.amazon.com/opsworks/home?#/stack/12345678-1234-1234-1234-123456789012/deployments", + "description" => "Deployment 721 Accepted by Amazon. (github-services@#{Service.current_sha[0..7]})" + } + + github_deployment_path = "/repos/atmos/my-robot/deployments/721/statuses" + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + custom_sample_data = sample_data.merge('github_token' => 'secret') + svc = service(:deployment, custom_sample_data, environmental_payload) + response = svc.receive_event + app_id = opsworks_deployment_environments['staging']['app_id'] + assert_equal app_id, response.request_options[:app_id] + + @stubs.verify_stubbed_calls + end + + def aws_service(data = sample_data, payload = sample_payload) + Service::AwsOpsWorks.new(:deployment, data, payload) + end + + def service_class + Service::AwsOpsWorks + end + + def sample_data + { + 'aws_access_key_id' => 'AKIA1234567890123456', + 'aws_secret_access_key' => '0123456789+0123456789+0123456789+0123456', + 'stack_id' => '12345678-1234-1234-1234-123456789012', + 'app_id' => '01234567-0123-0123-0123-012345678901', + 'branch_name' => 'default-branch' + } + end + + def opsworks_deployment_environments + { + 'staging' => { + 'app_id' => '01234567-0123-0123-0123-012345678901', + 'stack_id' => '12345678-1234-1234-1234-123456789012', + }, + 'production' => { + 'app_id' => '01234567-0123-0123-0123-012345678902', + 'stack_id' => '12345678-1234-1234-1234-123456789013', + }, + 'qa' => { + 'app_id' => '01234567-0123-0123-0123-012345678903', + 'stack_id' => '12345678-1234-1234-1234-123456789012', + } + } + end + + def environmental_payload + custom_payload = { + 'environment' => 'staging', + 'payload' => { + 'config' => { + 'opsworks' => opsworks_deployment_environments + } + } + } + Service::DeploymentHelpers.sample_deployment_payload.merge(custom_payload) + end + + def sample_payload(branch_name = 'default-branch') + Service::DeploymentHelpers.sample_deployment_payload.merge('ref' => "refs/heads/#{branch_name}") + end +end diff --git a/test/aws_ops_works_test.rb b/test/aws_ops_works_test.rb new file mode 100644 index 000000000..659bd10dc --- /dev/null +++ b/test/aws_ops_works_test.rb @@ -0,0 +1,104 @@ +require File.expand_path('../helper', __FILE__) + +class AwsOpsWorksTest < Service::TestCase + + def setup + AWS.stub! + end + + def test_stack_id_sent + response = service.receive_event + assert_equal sample_data['stack_id'], response.request_options[:stack_id] + end + + def test_stack_id_missing + svc = service(sample_data.except('stack_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_app_id_sent + response = service.receive_event + assert_equal sample_data['app_id'], response.request_options[:app_id] + end + + def test_app_id_missing + svc = service(sample_data.except('app_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_expected_branch_name_received + response = service.receive_event + refute_nil response + end + + def test_unexpected_branch_name_received + response = service(sample_data, sample_payload('another-branch')).receive_event + assert_nil response + end + + def test_branch_name_missing + svc = service(sample_data.except('branch_name')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_access_key_id_configured + config = service.ops_works_client.config + assert_equal sample_data['aws_access_key_id'], config.access_key_id + end + + def test_region_configured + config = service.ops_works_client.config + assert_equal sample_data['region'], config.ops_works_region + end + + def test_aws_access_key_id_missing + svc = service(sample_data.except('aws_access_key_id')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_aws_secret_access_key_configured + config = service.ops_works_client.config + assert_equal sample_data['aws_secret_access_key'], config.secret_access_key + end + + def test_aws_secret_access_key_missing + svc = service(sample_data.except('aws_secret_access_key')) + assert_raises Service::ConfigurationError do + svc.receive_event + end + end + + def test_region_blank + svc = service(sample_data.merge('region' => "")) + config = service.ops_works_client.config + assert_equal sample_data['region'], config.ops_works_region + end + + def service(data = sample_data, payload = sample_payload) + Service::AwsOpsWorks.new(:push, data, payload) + end + + def sample_data + { + 'aws_access_key_id' => 'AKIA1234567890123456', + 'aws_secret_access_key' => '0123456789+0123456789+0123456789+0123456', + 'stack_id' => '12345678-1234-1234-1234-123456789012', + 'app_id' => '01234567-0123-0123-0123-012345678901', + 'region' => "us-east-1", + 'branch_name' => 'default-branch' + } + end + + def sample_payload(branch_name = 'default-branch') + Service::PushHelpers.sample_payload.merge('ref' => "refs/heads/#{branch_name}") + end + +end diff --git a/test/backlog_test.rb b/test/backlog_test.rb new file mode 100644 index 000000000..dfb06884b --- /dev/null +++ b/test/backlog_test.rb @@ -0,0 +1,54 @@ +require File.expand_path('../helper', __FILE__) + +class BacklogTest < Service::TestCase + def setup + @server = FakeXMLRPC.new() + end + + def test_push + modified_payload = modify_payload(payload) + svc = service({'api_url' => 'https://demo.backlog.jp/XML-RPC', 'user_id' => 'someone', 'password' => '12345'}, modified_payload) + svc.xmlrpc_client = @server + svc.receive_push + + assert @server.commented.length == 1 + assert @server.commented[0]['content'].include? '06f63b43050935962f84fe54473a7c5de7977325' + assert @server.switched.length == 2 + assert @server.switched[0]['comment'].include? '5057e76a11abd02e83b7d3d3171c4b68d9c88480' + assert @server.switched[0]['status'] == 3 + assert @server.switched[1]['comment'].include? 'a47fd41f3aa4610ea527dcc1669dfdb9c15c5425' + assert @server.switched[1]['status'] == 4 + end + + def modify_payload(payload) + modified_payload = payload.clone() + modified_payload['commits'][0]['message'] << "\nDORA-1" + modified_payload['commits'][1]['message'] << "\nDORA-2 #fixed" + modified_payload['commits'][2]['message'] << "\nDORA-3 #closed" + return modified_payload + end + + def service(*args) + super Service::Backlog, *args + end + + class FakeXMLRPC + def call(procedure, arguments) + case procedure + when 'backlog.addComment' + commented << {'key' => arguments['key'], 'content' => arguments['content']} + when 'backlog.switchStatus' + switched << { 'key' => arguments['key'], 'comment' => arguments['comment'], 'status' => arguments['statusId']} + end + end + + def commented + @commented ||= [] + end + + def switched + @switched ||= [] + end + end +end + diff --git a/test/bamboo_test.rb b/test/bamboo_test.rb index 54cbc3b2e..1319205f1 100644 --- a/test/bamboo_test.rb +++ b/test/bamboo_test.rb @@ -1,29 +1,15 @@ require File.expand_path('../helper', __FILE__) -Service::App.set :environment, :test class BambooTest < Service::TestCase EXAMPLE_BASE_URL = "http://bamboo.example.com".freeze - def app - Service::App - end - def setup @stubs = Faraday::Adapter::Test::Stubs.new end def test_triggers_build - @stubs.post "/api/rest/login.action" do |env| - assert_params env[:body], :username => 'admin', :password => 'pwd' - [200, {}, 'TOKEN123'] - end - @stubs.post "/api/rest/executeBuild.action" do |env| - assert_params env[:body], :auth => "TOKEN123", :buildKey => "ABC" - [200, {}, ''] - end - @stubs.post "/api/rest/logout.action" do |env| - assert_equal "auth=TOKEN123", env[:body] - [200, {}, ''] + @stubs.post "/rest/api/latest/queue/ABC" do |env| + valid_response("ABC") end svc = service :push, data, payload @@ -33,21 +19,10 @@ def test_triggers_build end def test_triggers_compound_build - @stubs.post "/api/rest/login.action" do |env| - assert_params env[:body], :username => 'admin', :password => 'pwd' - [200, {}, 'TOKEN123'] - end - @stubs.post "/api/rest/executeBuild.action" do |env| - assert_params env[:body], :auth => "TOKEN123", :buildKey => "ABC" - [200, {}, ''] - end - @stubs.post "/api/rest/executeBuild.action" do |env| - assert_params env[:body], :auth => "TOKEN123", :buildKey => "A" - [200, {}, ''] - end - @stubs.post "/api/rest/logout.action" do |env| - assert_equal "auth=TOKEN123", env[:body] - [200, {}, ''] + ["ABC", "A"].each do |key| + @stubs.post "/rest/api/latest/queue/#{key}" do |env| + valid_response(key) + end end svc = service :push, compound_data1, payload @@ -57,17 +32,8 @@ def test_triggers_compound_build end def test_triggers_build_with_context_path - @stubs.post "/context/api/rest/login.action" do |env| - assert_params env[:body], :username => 'admin', :password => 'pwd' - [200, {}, 'TOKEN123'] - end - @stubs.post "/context/api/rest/executeBuild.action" do |env| - assert_params env[:body], :auth => "TOKEN123", :buildKey => "ABC" - [200, {}, ''] - end - @stubs.post "/context/api/rest/logout.action" do |env| - assert_equal "auth=TOKEN123", env[:body] - [200, {}, ''] + @stubs.post "/context/rest/api/latest/queue/ABC" do |env| + valid_response("ABC") end data = self.data.update('base_url' => "https://secure.bamboo.com/context") @@ -78,44 +44,36 @@ def test_triggers_build_with_context_path end def test_passes_build_error - @stubs.post "/api/rest/login.action" do |env| - assert_params env[:body], :username => 'admin', :password => 'pwd' - [200, {}, 'TOKEN123'] - end - @stubs.post "/api/rest/executeBuild.action" do |env| - assert_params env[:body], :auth => "TOKEN123", :buildKey => "ABC" - [200, {}, 'oh hai'] - end - @stubs.post "/api/rest/logout.action" do |env| - assert_equal "auth=TOKEN123", env[:body] - [200, {}, ''] + @stubs.post "/rest/api/latest/queue/ABC" do |env| + error_response(500, "Configuration Error") end svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end @stubs.verify_stubbed_calls end - def test_requires_valid_login - @stubs.post "/api/rest/login.action" do - [401, {}, ''] + def test_passes_error_with_xml_parsing + @stubs.post "/rest/api/latest/queue/ABC" do |env| + error_response(404, "Plan ABC not found") end svc = service :push, data, payload - - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end + + @stubs.verify_stubbed_calls end def test_requires_base_url data = self.data.update('base_url' => '') svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end @@ -124,7 +82,7 @@ def test_requires_build_key data = self.data.update('build_key' => '') svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end @@ -133,7 +91,7 @@ def test_requires_username data = self.data.update('username' => '') svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end @@ -142,7 +100,7 @@ def test_requires_password data = self.data.update('password' => '') svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end @@ -153,23 +111,36 @@ def svc.http_post(*args) raise SocketError, "getaddrinfo: Name or service not known" end - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end def test_invalid_bamboo_url - @stubs.post "/api/rest/login.action" do + @stubs.post "/rest/api/latest/queue/ABC" do [404, {}, ''] end svc = service :push, data, payload - assert_raise Service::ConfigurationError do + assert_raises Service::ConfigurationError do svc.receive end end + def test_branch_with_slash + data = self.data.update('build_key' => 'test/test:ABC') + payload = self.payload.update('ref' => 'test/test') + @stubs.post "/rest/api/latest/queue/ABC" do |env| + valid_response("ABC") + end + + svc = service :push, data, payload + svc.receive + + @stubs.verify_stubbed_calls + end + def data { "build_key" => "ABC", @@ -196,7 +167,7 @@ def compound_data1 # Raises Test::Unit::AssertionFailedError if the assertion doesn't match. # Returns nothing. def assert_params(body, expected) - params = Rack::Utils.parse_query(body) + params = Faraday::Utils.parse_query(body) expected.each do |key, expected_value| assert value = params.delete(key.to_s), "#{key} not in #{params.inspect}" assert_equal expected_value, value, "#{key} = #{value.inspect}, not #{expected_value.inspect}" @@ -208,5 +179,18 @@ def assert_params(body, expected) def service(*args) super Service::Bamboo, *args end + + private + def valid_response(key) + xml = "" + + "" + + "Manual build" + + "" + [200, {}, xml] + end + def error_response(status, msg) + xml = "#{status}#{msg}" + [status, {}, xml] + end end diff --git a/test/basecamp_test.rb b/test/basecamp_test.rb index 09b426f43..58d764987 100644 --- a/test/basecamp_test.rb +++ b/test/basecamp_test.rb @@ -1,30 +1,90 @@ require File.expand_path('../helper', __FILE__) class BasecampTest < Service::TestCase - def test_receives_push - svc = service :push, {'url' => 'https://foo.com', 'username' => 'monkey', 'password' => 'abc'}, payload - svc.receive - - assert msg = svc.messages.shift - assert_equal 2, msg.category_id - assert msg.title.present? - assert msg.body.present? + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @options = { + 'project_url' => 'https://basecamp.com/123/projects/456', + 'email_address' => 'a@b.com', + 'password' => 'secret' } end - def service(*args) - svc = super Service::Basecamp, *args + def test_push + @stubs.post '/123/api/v1/projects/456/events.json' do |env| + assert_equal 'https', env[:url].scheme + assert_equal 'basecamp.com', env[:url].host + + assert_equal 'Basic YUBiLmNvbTpzZWNyZXQ=', env[:request_headers]['Authorization'] - svc.project_id = 1 - svc.category_id = 2 + assert_match 'GitHub', env[:request_headers]['User-Agent'] + assert_equal 'application/json', env[:request_headers]['Content-Type'] + assert_equal 'application/json', env[:request_headers]['Accept'] - def svc.messages - @messages ||= [] + expected = { + 'service' => Service::Basecamp::SERVICE_NAME, + 'logo_url' => Service::Basecamp::LOGO_URL, + 'creator_email_address' => 'tom@mojombo.com', + 'description' => 'committed', + 'title' => 'pushed 3 new commits to master', + 'url' => 'http://github.com/mojombo/grit/compare/4c8124f...a47fd41' } + assert_equal expected, JSON.parse(env[:body]) + + [200, {}, ''] end - def svc.post_message(options = {}) - messages << build_message(options) + service(@options, payload).receive_push + end + + def test_pull + @stubs.post '/123/api/v1/projects/456/events.json' do |env| + expected = { + 'service' => Service::Basecamp::SERVICE_NAME, + 'logo_url' => Service::Basecamp::LOGO_URL, + 'creator_email_address' => nil, + 'description' => 'opened a pull request', + 'title' => 'booya (master..feature)', + 'url' => 'html_url' } + assert_equal expected, JSON.parse(env[:body]) + + [200, {}, ''] end - svc + service(:pull_request, @options, pull_payload).receive_pull_request + end + + def test_issues + @stubs.post '/123/api/v1/projects/456/events.json' do |env| + expected = { + 'service' => Service::Basecamp::SERVICE_NAME, + 'logo_url' => Service::Basecamp::LOGO_URL, + 'creator_email_address' => nil, + 'description' => 'opened an issue', + 'title' => 'booya', + 'url' => 'html_url' } + assert_equal expected, JSON.parse(env[:body]) + + [200, {}, ''] + end + + service(:issues, @options, issues_payload).receive_issues + end + + def service(*args) + super Service::Basecamp, *args + end + + # No html_url in default payload + def pull_payload + super.tap do |payload| + payload['pull_request']['html_url'] = 'html_url' + end + end + + # No html_url in default payload + def issues_payload + super.tap do |payload| + payload['issue']['html_url'] = 'html_url' + end end end diff --git a/test/boxcar_test.rb b/test/boxcar_test.rb deleted file mode 100644 index 2455b2a15..000000000 --- a/test/boxcar_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class BoxcarTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service :push, {'subscribers' => 'abc'}, 'a' => 1 - svc.secrets = {'boxcar' => {'apikey' => 'key'}} - - @stubs.post "/github/key" do |env| - assert_match /(^|\&)emails=abc($|\&)/, env[:body] - assert_match /(^|\&)payload=%7B%22a%22%3A1%7D($|\&)/, env[:body] - [200, {}, ''] - end - - svc.receive - - @stubs.verify_stubbed_calls - end - - def service(*args) - super Service::Boxcar, *args - end -end - diff --git a/test/bugherd_test.rb b/test/bugherd_test.rb index 2014492fc..7e09dae9e 100644 --- a/test/bugherd_test.rb +++ b/test/bugherd_test.rb @@ -1,20 +1,23 @@ -class BugherdTest < Service::TestCase +require File.expand_path('../helper', __FILE__) + +class BugHerdTest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new end def test_push - @stubs.post "/custom" do |env| - assert_equal 'www.example.com', env[:url].host + @stubs.post "/github_web_hook/KEY" do |env| + assert_equal 'www.bugherd.com', env[:url].host assert_equal 'application/x-www-form-urlencoded', env[:request_headers]['content-type'] [200, {}, ''] end - svc = service :push, - {'url' => 'http://www.example.com/custom', 'project_key' => 'KEY'}, payload + svc = service :push, {'project_key' => 'KEY'}, payload svc.receive_push + end + def test_issues @stubs.post "/github_web_hook/KEY" do |env| assert_equal 'www.bugherd.com', env[:url].host assert_equal 'application/x-www-form-urlencoded', @@ -22,12 +25,23 @@ def test_push [200, {}, ''] end - svc = service :push, - {'url' => '', 'project_key' => 'KEY'}, payload - svc.receive_push + svc = service :push, {'project_key' => 'KEY'}, payload + svc.receive_issues + end + + def test_issue_comment + @stubs.post "/github_web_hook/KEY" do |env| + assert_equal 'www.bugherd.com', env[:url].host + assert_equal 'application/x-www-form-urlencoded', + env[:request_headers]['content-type'] + [200, {}, ''] + end + + svc = service :push, {'project_key' => 'KEY'}, payload + svc.receive_issue_comment end def service(*args) - super Service::Bugherd, *args + super Service::BugHerd, *args end end diff --git a/test/bugly_test.rb b/test/bugly_test.rb deleted file mode 100644 index 0a17c2531..000000000 --- a/test/bugly_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class BuglyTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service :push, {'token' => 'abc'}, 'a' => 1 - - @stubs.post "/changesets.json" do |env| - assert_equal %({"a":1}), env[:body] - assert_equal 'abc', env[:request_headers]['X-BuglyToken'] - assert_equal 'application/json', env[:request_headers]['Content-Type'] - [200, {}, ''] - end - - svc.receive - - @stubs.verify_stubbed_calls - end - - def service(*args) - super Service::Bugly, *args - end -end - - - diff --git a/test/bugzilla_test.rb b/test/bugzilla_test.rb index f4875a1f1..28f4182ce 100644 --- a/test/bugzilla_test.rb +++ b/test/bugzilla_test.rb @@ -7,7 +7,7 @@ def setup def test_push modified_payload = modify_payload(payload) - svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => false}, modified_payload) + svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => '0'}, modified_payload) svc.xmlrpc_client = @server svc.receive_push @@ -24,7 +24,7 @@ def test_push # Verify pushes will be processed on all commits if no integration branch is specified. def test_integration_branch_is_optional modified_payload = modify_payload(payload) - svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => true}, modified_payload) + svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => '1'}, modified_payload) svc.xmlrpc_client = @server svc.receive_push @@ -37,7 +37,7 @@ def test_integration_branch # No commits should be processed for this push because we're only listening for # commits landing on the "master" branch. modified_payload = modify_payload(payload).merge({'ref' => 'refs/heads/development'}) - svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'integration_branch' => 'master', 'central_repository' => true}, modified_payload) + svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'integration_branch' => 'master', 'central_repository' => '1'}, modified_payload) svc.xmlrpc_client = @server svc.receive_push @@ -47,7 +47,7 @@ def test_integration_branch # This time, we should close a bug and post 4 comments because these commits were # pushed to our integration branch. modified_payload = modify_payload(payload).merge({'ref' => 'refs/heads/master'}) - svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'integration_branch' => 'master', 'central_repository' => true}, modified_payload) + svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'integration_branch' => 'master', 'central_repository' => '1'}, modified_payload) svc.xmlrpc_client = @server svc.receive_push @@ -58,7 +58,7 @@ def test_integration_branch def test_central_push #test pushing to a central repository modified_payload = modify_payload(payload) - svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => true}, modified_payload) + svc = service({'server_url' => 'nowhere', 'username' => 'someone', 'password' => '12345', 'central_repository' => '1'}, modified_payload) svc.xmlrpc_client = @server svc.receive_push diff --git a/test/campfire_test.rb b/test/campfire_test.rb index 962b23fe9..1ec58af2c 100644 --- a/test/campfire_test.rb +++ b/test/campfire_test.rb @@ -51,7 +51,7 @@ def test_issues assert_equal 't', svc.campfire.token assert_equal 'r', svc.campfire.rooms.first.name assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary - assert_match /\[grit\] mojombo opened issue #5: booya./i, svc.campfire.rooms.first.lines.first + assert_match /\[grit\] defunkt opened issue #5: booya./i, svc.campfire.rooms.first.lines.first end def test_pull @@ -62,7 +62,64 @@ def test_pull assert_equal 't', svc.campfire.token assert_equal 'r', svc.campfire.rooms.first.name assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary - assert_match /\[grit\] mojombo opened pull request #5: booya \(master...feature\)/i, svc.campfire.rooms.first.lines.first + assert_match /\[grit\] defunkt opened pull request #5: booya \(master...feature\)/i, svc.campfire.rooms.first.lines.first + end + + def test_public + svc = service(:public, {"token" => "t", "subdomain" => "s", "room" => "r"}, public_payload) + svc.receive_public + assert_equal 1, svc.campfire.rooms.size + assert_equal 's', svc.campfire.subdomain + assert_equal 't', svc.campfire.token + assert_equal 'r', svc.campfire.rooms.first.name + assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary + assert_match /\[grit\] defunkt made the repository public/i, svc.campfire.rooms.first.lines.first + end + + def test_gollum + svc = service(:gollum, {"token" => "t", "subdomain" => "s", "room" => "r"}, gollum_payload) + svc.receive_public + assert_equal 1, svc.campfire.rooms.size + assert_equal 's', svc.campfire.subdomain + assert_equal 't', svc.campfire.token + assert_equal 'r', svc.campfire.rooms.first.name + assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary + assert_match /\[grit\] defunkt created wiki page Foo/i, svc.campfire.rooms.first.lines.first + end + + def test_gollum_multiple_pages + multiple_page_payload = gollum_payload + multiple_page_payload['pages'] << multiple_page_payload['pages'][0].merge( + 'title' => 'Bar', + 'html_url' => 'https://github.com/mojombo/magik/wiki/Bar', + ) + + svc = service(:gollum, {"token" => "t", "subdomain" => "s", "room" => "r"}, multiple_page_payload) + svc.receive_public + assert_equal 1, svc.campfire.rooms.size + assert_equal 's', svc.campfire.subdomain + assert_equal 't', svc.campfire.token + assert_equal 'r', svc.campfire.rooms.first.name + assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary + assert_match /\[grit\] defunkt created 2 wiki pages/i, svc.campfire.rooms.first.lines.first + end + + def test_gollum_multiple_actions + multiple_action_payload = gollum_payload + multiple_action_payload['pages'] << multiple_action_payload['pages'][0].merge( + 'title' => 'Bar', + 'html_url' => 'https://github.com/mojombo/magik/wiki/Bar', + 'action' => 'updated' + ) + + svc = service(:gollum, {"token" => "t", "subdomain" => "s", "room" => "r"}, multiple_action_payload) + svc.receive_public + assert_equal 1, svc.campfire.rooms.size + assert_equal 's', svc.campfire.subdomain + assert_equal 't', svc.campfire.token + assert_equal 'r', svc.campfire.rooms.first.name + assert_equal 1, svc.campfire.rooms.first.lines.size # 3 + summary + assert_match /\[grit\] defunkt created 1 and updated 1 wiki pages/i, svc.campfire.rooms.first.lines.first end def test_full_domain @@ -123,4 +180,3 @@ def shorten_url(*args) svc end end - diff --git a/test/codeclimate_test.rb b/test/codeclimate_test.rb index ac9aecbb7..b3ec40a49 100644 --- a/test/codeclimate_test.rb +++ b/test/codeclimate_test.rb @@ -3,7 +3,7 @@ class CodeClimateTest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new - @svc = service(data, payload) + @svc = service(data, {'commits'=>[{'id'=>'test'}]}) end def test_reads_token_from_data @@ -16,15 +16,16 @@ def test_strips_whitespace_from_token end def test_posts_payload - @stubs.post '/github_pushes' do |env| + @stubs.post '/github_events' do |env| assert_equal 'https', env[:url].scheme assert_equal 'codeclimate.com', env[:url].host assert_equal basic_auth('github', '5373dd4a3648b88fa9acb8e46ebc188a'), env[:request_headers]['authorization'] - assert_equal payload, JSON.parse(Rack::Utils.parse_query(env[:body])['payload']) + assert JSON.parse(env[:body]).keys.include?("payload") + assert_equal "test", JSON.parse(env[:body])["payload"]["commits"][0]["id"] end - @svc.receive_push + @svc.receive_event end private diff --git a/test/codeship_test.rb b/test/codeship_test.rb new file mode 100644 index 000000000..05ff7c71e --- /dev/null +++ b/test/codeship_test.rb @@ -0,0 +1,29 @@ +require File.expand_path('../helper', __FILE__) + +class CodeshipTest < Service::TestCase + include Service::HttpTestMethods + + def test_push_event + project_uuid = '2fe6bdb0-a6db-0131-f25b-0088653824b4' + + data = { 'project_uuid' => project_uuid } + + svc = service(:push, data, payload) + + @stubs.post "/github/#{project_uuid}" do |env| + body = Faraday::Utils.parse_query env[:body] + assert_equal "https://lighthouse.codeship.io/github/#{project_uuid}", env[:url].to_s + assert_match 'application/x-www-form-urlencoded', env[:request_headers]['Content-Type'] + assert_equal 'push', env[:request_headers]['X-GitHub-Event'] + assert_equal payload, JSON.parse(body["payload"].to_s) + end + + svc.receive_event + end + + private + + def service_class + Service::Codeship + end +end diff --git a/test/commandoio_test.rb b/test/commandoio_test.rb new file mode 100644 index 000000000..ef6afa9cf --- /dev/null +++ b/test/commandoio_test.rb @@ -0,0 +1,35 @@ +require File.expand_path('../helper', __FILE__) + +class Commandoio < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "v1/recipes/_______mock_recipe_______/execute" do |env| + body = Faraday::Utils.parse_query(env[:body]) + payload = JSON.parse(body['payload']) + + assert_equal 'api.commando.io', env[:url].host + assert_equal 'https', env[:url].scheme + assert_match 'application/x-www-form-urlencoded', env[:request_headers]['content-type'] + assert_equal payload, JSON.parse(Faraday::Utils.parse_query(env[:body])['payload']) + assert_equal basic_auth('demo', 'skey_abcdsupersecretkey'), + env[:request_headers]['authorization'] + [200, {}, ''] + end + + svc = service :push, { + 'api_token_secret_key' => 'skey_abcdsupersecretkey', + 'account_alias' => 'demo', + 'recipe' => '_______mock_recipe_______', + 'server' => '_server_', + 'notes' => 'Test the mock recipe!' + } + svc.receive_event + end + + def service(*args) + super Service::Commandoio, *args + end +end diff --git a/test/conductor_test.rb b/test/conductor_test.rb new file mode 100644 index 000000000..0baabbdb6 --- /dev/null +++ b/test/conductor_test.rb @@ -0,0 +1,24 @@ +require File.expand_path('../helper', __FILE__) + +class ConductorTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + url = "/github/commit/abc123def456" + @stubs.post url do |env| + body = Faraday::Utils.parse_query(env[:body]) + payload = JSON.parse(body['payload']) + assert_equal payload['ref'], 'refs/heads/master' + [200, {}, ''] + end + + svc = service({'api_key' => 'abc123def456'}, payload) + svc.receive_push + end + + def service(*args) + super Service::Conductor, *args + end +end diff --git a/test/convore_test.rb b/test/convore_test.rb deleted file mode 100644 index d6849fc59..000000000 --- a/test/convore_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class ConvoreTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - url = "/api/topics/1/messages/create.json" - @stubs.post url do |env| - assert_equal 'application/x-www-form-urlencoded', env[:request_headers]["Content-Type"] - assert_equal basic_auth(:rick, :monkey), env[:request_headers]['authorization'] - assert_match /grit/, env[:body] - [200, {}, '{}'] - end - - svc = service({ - 'topic_id' => '1', - 'username' => 'rick', - 'password' => 'monkey' - }, payload) - svc.receive_push - end - - def service(*args) - super Service::Convore, *args - end -end - diff --git a/test/coop_test.rb b/test/coop_test.rb deleted file mode 100644 index 331e417db..000000000 --- a/test/coop_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class CoOpTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({'group_id' => 'abc', 'token' => 'def'}, payload) - - num = 0 - @stubs.post "/groups/abc/notes" do |env| - data = JSON.parse env[:body] - assert_match /tom/i, data['status'] - assert_equal 'def', data['key'] - - assert_equal 'GitHub Notifier', env[:request_headers]['User-Agent'] - assert_equal 'application/json; charset=utf-8', - env[:request_headers]['Content-Type'] - assert_equal 'application/json', - env[:request_headers]['Accept'] - - num += 1 - - [200, {}, ''] - end - - svc.receive_push - assert_equal 3, num - - @stubs.verify_stubbed_calls - end - - def service(*args) - super Service::CoOp, *args - end -end - - - diff --git a/test/copperegg_test.rb b/test/copperegg_test.rb new file mode 100644 index 000000000..b05ede77c --- /dev/null +++ b/test/copperegg_test.rb @@ -0,0 +1,66 @@ +require File.expand_path('../helper', __FILE__) + +class CopperEggTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @data = { + 'url' => 'https://api.copperegg.com/custom', + 'api_key' => '134567890', + 'tag' => 'newtag', + 'master_only' => '0' + } + @payload = { + "after" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "ref" => "refs/heads/master", + "before" => "4c8124ffcf4039d292442eeccabdeca5af5c5017", + "compare" => "http://github.com/mojombo/grit/compare/4c8124ffcf4039d292442eeccabdeca5af5c5017...a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "forced" => false, + "created" => false, + "deleted" => false, + + "repository" => { + "name" => "grit", + "url" => "http://github.com/mojombo/grit", + "owner" => { "name" => "mojombo", "email" => "tom@mojombo.com" } + }, + + "pusher" => { + "name" => "rtomayko" + }, + + "commits" => [ + { + "distinct" => true, + "removed" => [], + "message" => "[#WEB-249 status:31 resolution:1] stub git call for Grit#heads test", + "added" => [], + "timestamp" => "2007-10-10T00:11:02-07:00", + "modified" => ["lib/grit/grit.rb", "test/helper.rb", "test/test_grit.rb"], + "url" => "http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325", + "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, + "id" => "06f63b43050935962f84fe54473a7c5de7977325" + } + ] + } + end + + def test_push + svc = service(@data, @payload) + assert_equal 1, @payload['commits'].size + assert_equal 'rtomayko', @payload['pusher']['name'] + assert_equal 'grit', @payload['repository']['name'] + + @stubs.post "/custom" do |env| + assert_match "Basic MTM0NTY3ODkwOlU=", env[:request_headers]["authorization"] + assert_equal "application/json", env[:request_headers]["Content-Type"] + assert_match "GitHub: rtomayko has pushed 1 commit(s) to grit", env[:body] + assert_match "newtag", env[:body] + [200, {}, ''] + end + svc.receive_push + end + + def service(*args) + super Service::CopperEgg, *args + end +end diff --git a/test/crocagile_test.rb b/test/crocagile_test.rb new file mode 100644 index 000000000..f8c33ccaf --- /dev/null +++ b/test/crocagile_test.rb @@ -0,0 +1,22 @@ +require File.expand_path('../helper', __FILE__) + +class CrocagileTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "api/integration/github" do |env| + assert_equal 'www.crocagile.com', env[:url].host + assert_equal 'application/json', env[:request_headers]['content-type'] + [200, {}, '{"status":1,"message":"GitHub Webook processed successfully."}'] + end + svc = service({'project_key'=>'foo'},payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service(*args) + super Service::Crocagile, *args + end +end diff --git a/test/cube_test.rb b/test/cube_test.rb deleted file mode 100644 index 45869df51..000000000 --- a/test/cube_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class CubeTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - url = "/integration/events/github/create" - @stubs.post url do |env| - assert_match /(^|\&)payload=%7B%22a%22%3A1%7D($|\&)/, env[:body] - assert_match "project_name=p", env[:body] - assert_match "project_token=t", env[:body] - assert_match "domain=d", env[:body] - [200, {}, ''] - end - - svc = service( - {'project' => 'p', 'token' => 't', 'domain' => 'd'}, - 'a' => 1) - svc.receive_push - end - - def service(*args) - super Service::Cube, *args - end -end - - diff --git a/test/deploy_hq_test.rb b/test/deploy_hq_test.rb new file mode 100644 index 000000000..364489020 --- /dev/null +++ b/test/deploy_hq_test.rb @@ -0,0 +1,34 @@ +require File.expand_path('../helper', __FILE__) + +class DeployHqTest < Service::TestCase + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post '/deploy/projectname/to/servername/serverkey' do |env| + assert_equal 'test.deployhq.com', env[:url].host + assert_equal 'https', env[:url].scheme + post_payload = JSON.parse(Faraday::Utils.parse_query(env[:body])['payload']) + + refute_nil payload['after'] + assert_equal post_payload['after'], post_payload['after'] + refute_nil post_payload['ref'] + assert_equal payload['ref'], post_payload['ref'] + refute_nil post_payload['repository']['url'] + assert_equal payload['repository']['url'], post_payload['repository']['url'] + assert_equal payload['pusher']['email'], post_payload['pusher']['email'] + + [201, [], ''] + end + + svc = service :push, { 'deploy_hook_url' => 'https://test.deployhq.com/deploy/projectname/to/servername/serverkey' }, payload + svc.receive_push + end + + def service(*args) + super Service::DeployHq, *args + end + +end diff --git a/test/deployervc_test.rb b/test/deployervc_test.rb new file mode 100644 index 000000000..0b193d074 --- /dev/null +++ b/test/deployervc_test.rb @@ -0,0 +1,34 @@ +require File.expand_path('../helper', __FILE__) + +class DeployervcTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + test_deployment_address = 'https://emre.deployer.vc/#/deployment/1' + test_api_token = 'this_is_a_test_token' + + data = { + 'deployment_address' => test_deployment_address, + 'api_token' => test_api_token + } + + payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) + + @stubs.post '/api/v1/deployments/deploy/1' do |env| + body = JSON.parse(env[:body]) + + assert_equal env[:url].host, 'emre.deployer.vc' + assert_equal env[:request_headers]['X-Deployervc-Token'], test_api_token + assert_equal '', body['revision'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service_class + Service::Deployervc + end +end \ No newline at end of file diff --git a/test/divecloud_test.rb b/test/divecloud_test.rb new file mode 100644 index 000000000..e37716286 --- /dev/null +++ b/test/divecloud_test.rb @@ -0,0 +1,30 @@ +require File.expand_path('../helper', __FILE__) + +class DiveCloudTest < Service::TestCase + + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @data = { 'api_key' => 'Test_Api_Key', 'plan_id' => '833', 'creds_pass' => 'testtest', 'random_timing' => true, } + @payload = { 'status' => 'success' } + end + + def test_deployment_status + + @stubs.post "/api/v1/plans/833/run" do |env| + assert_equal "application/json", env[:request_headers]["Content-Type"] + assert_equal "Test_Api_Key", env[:request_headers]['x-api'] + [200, {}, ''] + end + + svc = service(:deployment_status, @data, @payload) + svc.receive_deployment_status + + end + + def service(*args) + super Service::DiveCloud, *args + end + + +end \ No newline at end of file diff --git a/test/djangopackages_test.rb b/test/djangopackages_test.rb new file mode 100644 index 000000000..3d9dd9d91 --- /dev/null +++ b/test/djangopackages_test.rb @@ -0,0 +1,24 @@ +require File.expand_path('../helper', __FILE__) + +class DjangoPackagesTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/packages/github-webhook/" do |env| + assert_equal 'www.djangopackages.com', env[:url].host + data = Faraday::Utils.parse_query(env[:body]) + assert_equal 1, JSON.parse(data['payload'])['a'] + [200, {}, ''] + end + + svc = service({"zen" => "test", "hook_id" => "123" }, :a => 1) + svc.receive_push + end + + def service(*args) + super Service::DjangoPackages, *args + end +end + diff --git a/test/docker_test.rb b/test/docker_test.rb new file mode 100644 index 000000000..85d438313 --- /dev/null +++ b/test/docker_test.rb @@ -0,0 +1,30 @@ +require File.expand_path('../helper', __FILE__) + +class DockerTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + data = {} + + payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) + + @stubs.post "/hooks/github" do |env| + body = JSON.parse(env[:body]) + + assert_equal env[:url].host, "registry.hub.docker.com" + assert_equal 'test', body['payload']['commits'][0]['id'] + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service_class + Service::Docker + end +end diff --git a/test/ducksboard_test.rb b/test/ducksboard_test.rb deleted file mode 100644 index 082c0a0af..000000000 --- a/test/ducksboard_test.rb +++ /dev/null @@ -1,97 +0,0 @@ -class DucksboardTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def receive_helper(event) - # Our service is pretty simple, and so is the test: just check that - # the original payload and event are received on our side, - # where the parsing will happen. - svc = service(event, {'webhook_key' => '1234abcd'}, payload) - - @stubs.post '/1234abcd' do |env| - body = Rack::Utils.parse_nested_query(env[:body]) - recv = JSON.parse(body['content']) - assert_equal recv['payload'], payload - assert_equal recv['event'], event.to_s - [200, {}, ''] - end - - event_method = "receive_#{event}" - svc.send(event_method) - end - - def test_receive - [:push, :issues, :fork, :watch].each do |event| - receive_helper event - end - end - - def test_webhook_key_through_url - svc = service({ - 'webhook_key' => 'https://webhooks.ducksboard.com/abcd1234' - }, payload) - - @stubs.post '/abcd1234' do |env| - body = Rack::Utils.parse_nested_query(env[:body]) - recv = JSON.parse(body['content']) - assert_equal recv['payload'], payload - [200, {}, ''] - end - - svc.receive - end - - def test_many_webhook_keys - svc = service({ - 'webhook_key' => '1 2 https://webhooks.ducksboard.com/3 ' \ - 'https://webhooks.ducksboard.com/4 5 6' - }, payload) - - posted = [] - - (1..6).each do |endpoint| - @stubs.post "/#{endpoint}" do |env| - posted << endpoint - body = Rack::Utils.parse_nested_query(env[:body]) - recv = JSON.parse(body['content']) - assert_equal recv['payload'], payload - [200, {}, ''] - end - end - - svc.receive - - # check that only the 5 first keys are used - assert_equal posted, [1, 2, 3, 4, 5] - end - - def test_missing_webhook_key - svc = service({}, payload) - assert_raise Service::ConfigurationError do - svc.receive - end - end - - def test_invalid_webhook_key - svc = service({ - 'webhook_key' => 'foobar' # non-hex values - }, payload) - assert_raise Service::ConfigurationError do - svc.receive - end - end - - def test_invalid_key_of_many - svc = service({ - 'webhook_key' => 'abc123 foobar' # non-hex values - }, payload) - assert_raise Service::ConfigurationError do - svc.receive - end - end - - def service(*args) - super Service::Ducksboard, *args - end -end diff --git a/test/email_test.rb b/test/email_test.rb index 7fb02f910..54409fce0 100644 --- a/test/email_test.rb +++ b/test/email_test.rb @@ -1,6 +1,10 @@ require File.expand_path('../helper', __FILE__) class EmailTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + def test_push svc = service( {'address' => 'a'}, @@ -15,6 +19,21 @@ def test_push assert_nil svc.messages.shift end + def test_public + svc = service({'address' => 'a'}, basic_payload) + + svc.receive_public + + msg, from, to, subject = svc.messages.shift + assert_match "noreply@github.com", from + assert_equal 'a', to + assert_match "mojombo/grit has changed from Private to Public", subject + assert_match subject, msg + assert_match "github.com/mojombo/grit", msg + + assert_nil svc.messages.shift + end + def test_multiple_address svc = service( {'address' => ' a b c'}, @@ -48,6 +67,35 @@ def test_push_from_author assert_nil svc.messages.shift end + def test_push_from_do_not_reply + svc = service( + {'address' => 'a', 'send_from_author' => '0'}, + payload) + + svc.receive_push + + msg, from, to = svc.messages.shift + assert_match 'noreply@github.com', from + assert_equal 'a', to + + assert_nil svc.messages.shift + end + + + def test_push_from_do_not_reply_with_no_option_set + svc = service( + {'address' => 'a'}, + payload) + + svc.receive_push + + msg, from, to = svc.messages.shift + assert_match 'noreply@github.com', from + assert_equal 'a', to + + assert_nil svc.messages.shift + end + def service(*args) svc = super Service::Email, *args def svc.messages @@ -55,11 +103,9 @@ def svc.messages end def svc.send_mail(mail) - messages << [mail.to_s, mail.from.first, mail.to.first] + messages << [mail.to_s, mail.from.first, mail.to.first, mail.subject] end svc end end - - diff --git a/test/firebase_test.rb b/test/firebase_test.rb new file mode 100644 index 000000000..5e317df5a --- /dev/null +++ b/test/firebase_test.rb @@ -0,0 +1,85 @@ +require File.expand_path('../helper', __FILE__) + +class FirebaseTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post '/commits.json' do |env| + assert_equal 'test.firebaseio.com', env[:url].host + + compare = '' + body = JSON.parse(env[:body]) + payload['commits'].each do |commit| + if commit['id'] == body['id'] + compare = commit + end + end + + assert_equal compare, body + [200, {}, ''] + end + + svc = service :push, { + 'firebase' => 'https://test.firebaseio.com/commits' + }, payload + svc.receive_push + end + + def test_push_with_secret + @stubs.post '/commits.json' do |env| + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'auth' => '12345678abcdefgh'}, params) + end + + svc = service :push, { + 'firebase' => 'https://test.firebaseio.com/commits', + 'secret' => '12345678abcdefgh' + }, payload + svc.receive_push + end + + def test_push_with_suffix + @stubs.post '/commits.json' do |env| + assert_equal 'test.firebaseio.com', env[:url].host + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'auth' => '12345678abcdefgh'}, params) + [200, {}, ''] + end + + svc = service :push, { + 'firebase' => 'https://test.firebaseio.com/commits.json', + 'secret' => '12345678abcdefgh' + }, payload + svc.receive_push + end + + def test_without_firebase + assert_raises Service::ConfigurationError do + svc = service :push, + {'firebase' => ''}, payload + svc.receive_push + end + end + + def test_without_https + assert_raises Service::ConfigurationError do + svc = service :push, + {'firebase' => 'http://test.firebaseio.com'}, payload + svc.receive_push + end + end + + def test_invalid_scheme + assert_raises Service::ConfigurationError do + svc = service :push, + {'firebase' => 'ftp://test.firebaseio.com'}, payload + svc.receive_push + end + end + + def service(*args) + super Service::Firebase, *args + end +end diff --git a/test/fisheye_test.rb b/test/fisheye_test.rb new file mode 100644 index 000000000..fc0efbd54 --- /dev/null +++ b/test/fisheye_test.rb @@ -0,0 +1,209 @@ +require File.expand_path('../helper', __FILE__) + +class FishEyeTest < Service::TestCase + def app + Service::App + end + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def data_my_repo + { + "url_base" => "http://localhost:6060/foo", + "token" => "515848d216e3baa46e10d92f21f890f67fea1d12", + "repository_name" => "myRepo" + } + end + + def assert_headers_valid(env) + assert_equal(data_my_repo["token"], env[:request_headers]["X-Api-Key"]) + assert_equal("application/json", env[:request_headers]["Content-Type"]) + end + + def test_triggers_scanning_custom_repository + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + assert_headers_valid(env) + [200, {}] + end + + svc = service :push, data_my_repo, payload + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_url_with_slash + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + assert_headers_valid(env) + [200, {}] + end + + data = data_my_repo + data['url_base'] = "http://localhost:6060/foo/" + + svc = service :push, data, payload + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_url_without_http + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + assert_headers_valid(env) + [200, {}] + end + + data = data_my_repo + data['url_base'] = "localhost:6060/foo" + + svc = service :push, data, payload + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_github_repository + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/grit/scan" do |env| + [200, {}] + end + + data = { + "url_base" => "http://localhost:6060/foo", + "token" => "515848d216e3baa46e10d92f21f890f67fea1d12", + } + + svc = service :push, data, payload + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_empty_custom_repository + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/grit/scan" do |env| + assert_headers_valid(env) + [200, {}] + end + + data = { + "url_base" => "http://localhost:6060/foo", + "token" => "515848d216e3baa46e10d92f21f890f67fea1d12", + "repository_name" => " " + } + + svc = service :push, data, payload + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_missing_url_base + data = { + "token" => "515848d216e3baa46e10d92f21f890f67fea1d12" + } + + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_missing_token + data = { + "url_base" => "http://localhost:6060/foo" + } + + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_missing_data + svc = service :push, {}, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_missing_data_and_payload + svc = service :push, {}, {} + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_missing_payload + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + assert_headers_valid(env) + [200, {}] + end + + svc = service :push, data_my_repo, {} + assert_equal("Ok", svc.receive_push) + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_error_401 + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + [401, {}] + end + + svc = service :push, data_my_repo, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_error_404 + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + [404, {}] + end + + svc = service :push, data_my_repo, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def test_triggers_scanning_error_other + @stubs.post "/foo/rest-service-fecru/admin/repositories-v1/myRepo/scan" do |env| + [409, {}] + end + + svc = service :push, data_my_repo, payload + + assert_raises Service::ConfigurationError do + svc.receive_push + end + + @stubs.verify_stubbed_calls + end + + def service(*args) + super Service::FishEye, *args + end + +end + + diff --git a/test/flowdock_test.rb b/test/flowdock_test.rb index 33666b832..a393a6f5f 100644 --- a/test/flowdock_test.rb +++ b/test/flowdock_test.rb @@ -3,29 +3,29 @@ class FlowdockTest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new - @token = "token" + @tokens = "token1,token2" end def test_push - @stubs.post "/v1/github/#{@token}" do |env| + @stubs.post "/v1/github/#{@tokens}" do |env| assert_match /json/, env[:request_headers]['content-type'] assert_equal push_payload, JSON.parse(env[:body]) [200, {}, ''] end svc = service( - {'token' => @token}, push_payload) + {'token' => @tokens}, push_payload) svc.receive_event end def test_token_sanitization - @stubs.post "/v1/github/#{@token}" do |env| + @stubs.post "/v1/github/#{@tokens}" do |env| assert_equal payload, JSON.parse(env[:body]) [200, {}, ''] end svc = service( - {'token' => " " + @token + " "}, payload) + {'token' => " " + @tokens + " "}, payload) svc.receive_event end diff --git a/test/freckle_test.rb b/test/freckle_test.rb index ae5df144c..7b51eaa79 100644 --- a/test/freckle_test.rb +++ b/test/freckle_test.rb @@ -1,78 +1,56 @@ require File.expand_path('../helper', __FILE__) class FreckleTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end + include Service::HttpTestMethods - def test_posts_with_2_entries - data = call_service :push - assert_equal 2, data['entries'].size - end + def test_push + test_subdomain = "test_subdomain" + test_token = "0123456789abcde" + test_project = "Test Project" - def test_includes_auth_token - data = call_service :push - assert_equal '12345', data['token'] - end + data = { + 'subdomain' => test_subdomain, + 'token' => test_token, + 'project' => test_project + } - def test_parses_minutes_from_commit_message - data = call_service :push - assert_equal '15', data['entries'][0]['minutes'] - assert_equal '2hrs', data['entries'][1]['minutes'] - end + # payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) - def test_strips_freckle_tags - data = call_service :push - assert_equal 'stub git call for Grit#heads test', - data['entries'][0]['description'] - assert_equal 'clean up heads test', - data['entries'][1]['description'] - end + @stubs.post "/api/github/commits" do |env| + body = JSON.parse(env[:body]) - def test_includes_project_name - data = call_service :push - assert_equal 'Test Project', - data['entries'][0]['project_name'] - end + assert_equal env[:url].host, "#{test_subdomain}.letsfreckle.com" + assert_equal env[:request_headers]['X-FreckleToken'], test_token + assert_equal env[:request_headers]['X-FreckleProject'], test_project + #3 entries + assert_equal 3, body['payload']['commits'].size - def test_includes_author_email_as_user - data = call_service :push - assert_equal 'tom@mojombo.com', - data['entries'][0]['user'] - end + #sends entire commit messages + assert_equal 'stub git call for Grit#heads test f:15 Case#1', body['payload']['commits'][0]['message'] + assert_equal 'clean up heads test f:2hrs', body['payload']['commits'][1]['message'] - def test_includes_commit_url - data = call_service :push - assert_equal 'http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325', - data['entries'][0]['url'] - end + #commit URL valid + assert_equal 'http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325', body['payload']['commits'][0]['url'] - def test_includes_timestamp_as_date - data = call_service :push - assert_equal '2007-10-10T00:11:02-07:00', - data['entries'][0]['date'] - end + #author email + assert_equal 'tom@mojombo.com', body['payload']['commits'][0]['author']['email'] - def data - { - "subdomain" => "abloom", - "token" => "12345", - "project" => "Test Project" - } - end + #timestamp + assert_equal '2007-10-10T00:11:02-07:00', body['payload']['commits'][0]['timestamp'] - def service(*args) - super Service::Freckle, *args + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls end - def call_service(event) - res = nil - svc = service data, payload - @stubs.post '/api/entries/import' do |env| - res = JSON.parse env[:body] - end - svc.send "receive_#{event}" - res + def service_class + Service::Freckle end end diff --git a/test/friend_feed_test.rb b/test/friend_feed_test.rb deleted file mode 100644 index 4564691c3..000000000 --- a/test/friend_feed_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class FriendFeedTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({'nickname' => 'n', 'remotekey' => 'r'}, payload) - - @stubs.post "/api/share" do |env| - assert_equal basic_auth(:n, :r), env[:request_headers][:authorization] - [200, {}, ''] - end - - svc.receive_push - end - - def service(*args) - super Service::FriendFeed, *args - end -end - diff --git a/test/gemini_test.rb b/test/gemini_test.rb new file mode 100644 index 000000000..b075e52da --- /dev/null +++ b/test/gemini_test.rb @@ -0,0 +1,25 @@ +require File.expand_path('../helper', __FILE__) + +class GeminiTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/gemini/api/github/commit" do |env| + assert_equal 'localhost', env[:url].host + assert_equal 'application/json', env[:request_headers]['content-type'] + [200, {}, ''] + end + + config = {'server_url' => 'http://localhost/gemini', + 'api_key' => '43904539-01DD-48DF-98F3-C887DE833C3H'} + + svc = service(config, payload) + svc.receive_push + end + + def service(*args) + super Service::Gemini, *args + end +end diff --git a/test/geocommit_test.rb b/test/geocommit_test.rb deleted file mode 100644 index 8b2ab05bd..000000000 --- a/test/geocommit_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class GeoCommitTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({}, 'a' => 1) - - @stubs.post "/api/github" do |env| - assert_equal 'application/githubpostreceive+json', - env[:request_headers]['Content-Type'] - assert_equal 1, JSON.parse(env[:body])['a'] - [200, {}, ''] - end - - svc.receive_push - end - - def service(*args) - super Service::GeoCommit, *args - end -end - diff --git a/test/git_live_test.rb b/test/git_live_test.rb deleted file mode 100644 index 3a8261a33..000000000 --- a/test/git_live_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class GitLiveTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({}, 'a' => 1) - - @stubs.post "/hook" do |env| - assert_match /(^|\&)payload=%7B%22a%22%3A1%7D($|\&)/, env[:body] - [200, {}, ''] - end - - svc.receive_push - end - - def service(*args) - super Service::GitLive, *args - end -end - - diff --git a/test/gitter_test.rb b/test/gitter_test.rb new file mode 100644 index 000000000..fa845470d --- /dev/null +++ b/test/gitter_test.rb @@ -0,0 +1,62 @@ +require File.expand_path('../helper', __FILE__) + +class GitterTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + data = {'token' => "0123456789abcde"} + + svc = service :push, data, push_payload + + @stubs.post "/e/#{data['token']}" do |env| + body = JSON.parse(env[:body]) + + #assert_equal env[:url].host, "webhooks.gitter.im" + assert_equal env[:request_headers]['X-GitHub-Event'], "push" + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_mute_fork + data = {'token' => "0123456789abcde", 'mute_fork' => "1"} + + svc = service :fork, data, basic_payload + svc.receive_event + assert @stubs.empty? + end + + def test_mute_watch + data = {'token' => "0123456789abcde", 'mute_watch' => "1"} + + svc = service :watch, data, basic_payload + svc.receive_event + assert @stubs.empty? + end + + def test_mute_comments + data = {'token' => "0123456789abcde", 'mute_comments' => "1"} + + svc = service :issue_comment, data, issue_comment_payload + svc.receive_event + assert @stubs.empty? + end + + def test_mute_wiki + data = {'token' => "0123456789abcde", 'mute_wiki' => "1"} + + svc = service :gollum, data, gollum_payload + svc.receive_event + assert @stubs.empty? + end + + def service_class + Service::Gitter + end +end + diff --git a/test/gocd_test.rb b/test/gocd_test.rb new file mode 100644 index 000000000..d6e4fb167 --- /dev/null +++ b/test/gocd_test.rb @@ -0,0 +1,98 @@ +require File.expand_path('../helper', __FILE__) + +class GoCDTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + end + + def test_push_deleted_branch + @stubs.post "go/api/material/notify/git" do + assert false, "service should not be called for deleted branches" + end + + svc = service :push, data, { "deleted" => true } + svc.receive + end + + def test_requires_base_url + data = self.data.update("base_url" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_requires_repository_url + data = self.data.update("repository_url" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_failed_go_server + svc = service :push, data, payload + def svc.http_post(*args) + raise SocketError, "getaddrinfo: Name or service not known" + end + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_invalid_go_url + @stubs.post "go/api/material/notify/git" do + [404, {}, ""] + end + + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_authorization_passed + @stubs.post "go/api/material/notify/git" do |env| + assert_equal basic_auth(:admin, :badger), env[:request_headers]['authorization'] + [200, {}, ""] + end + + svc = service :push, data, payload + + svc.receive + end + + def test_triggers_build + @stubs.post "go/api/material/notify/git" do |env| + assert_equal "localhost", env[:url].host + assert_equal 8153, env[:url].port + [200, {}, ""] + end + + svc = service :push, data, payload + svc.receive + + @stubs.verify_stubbed_calls + end + + def data + { + "base_url" => "http://localhost:8153", + "repository_url" => "git://github.com/gocd/gocd", + "username" => "admin", + "password" => "badger" + } + end + + def service(*args) + super Service::GoCD, *args + end +end + diff --git a/test/grmble_test.rb b/test/grmble_test.rb deleted file mode 100644 index 8cedffbaf..000000000 --- a/test/grmble_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class GrmbleTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({'room_api_url' => 'http://abc.com/foo'}, payload) - - @stubs.post "/foo/msg" do |env| - [200, {}, ''] - end - - svc.receive_push - end - - def service(*args) - super Service::Grmble, *args - end -end - diff --git a/test/habitualist_test.rb b/test/habitualist_test.rb new file mode 100644 index 000000000..f7d3aa879 --- /dev/null +++ b/test/habitualist_test.rb @@ -0,0 +1,24 @@ +require File.expand_path('../helper', __FILE__) + +class HabitualistTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/webhooks/github/" do |env| + assert_equal 'habitualist.com', env[:url].host + data = Faraday::Utils.parse_query(env[:body]) + assert_equal 1, JSON.parse(data['payload'])['a'] + [200, {}, ''] + end + + svc = service({}, :a => 1) + svc.receive_push + end + + def service(*args) + super Service::Habitualist, *args + end +end + diff --git a/test/helper.rb b/test/helper.rb index 052a447c9..b88d3d36e 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,10 +1,19 @@ -require 'test/unit' +require 'minitest/autorun' +require 'minitest/unit' +require 'pp' require File.expand_path('../../config/load', __FILE__) +Service.load_services + +class Service::TestCase < Minitest::Test + ALL_SERVICES = Service.services.dup -class Service::TestCase < Test::Unit::TestCase def test_default end + def assert_nothing_raised(*) + yield + end + def service(klass, event_or_data, data, payload=nil) event = nil if event_or_data.is_a?(Symbol) @@ -16,10 +25,8 @@ def service(klass, event_or_data, data, payload=nil) end service = klass.new(event, data, payload) - service.http = Faraday.new do |b| - b.request :url_encoded - b.adapter :test, @stubs - end + service.http :adapter => [:test, @stubs] + service.delivery_guid = "guid-#{rand}" service end @@ -35,9 +42,60 @@ def push_payload def pull_payload Service::PullRequestHelpers.sample_payload end + + def pull_request_payload + Service::PullRequestHelpers.sample_payload + end + + def pull_request_review_comment_payload + Service::PullRequestReviewCommentHelpers.sample_payload + end def issues_payload Service::IssueHelpers.sample_payload end + + def issue_comment_payload + Service::IssueCommentHelpers.sample_payload + end + + def commit_comment_payload + Service::CommitCommentHelpers.sample_payload + end + + def public_payload + Service::PublicHelpers.sample_payload + end + + def gollum_payload + Service::GollumHelpers.sample_payload + end + + def basic_payload + Service::HelpersWithMeta.sample_payload + end + + def deployment_payload + Service::DeploymentHelpers.sample_deployment_payload + end + + def status_payload + Service::StatusHelpers.sample_status_payload + end +end + +module Service::HttpTestMethods + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + + def service(event_or_data, data, payload = nil) + super(service_class, event_or_data, data, payload) + end + + def service_class + raise NotImplementedError + end end diff --git a/test/heroku_beta_test.rb b/test/heroku_beta_test.rb new file mode 100644 index 000000000..f3347febb --- /dev/null +++ b/test/heroku_beta_test.rb @@ -0,0 +1,207 @@ +require File.expand_path('../helper', __FILE__) + +class HerokuBetaTest < Service::TestCase + include Service::HttpTestMethods + + def heroku_service + data = { + 'name' => 'my-app', + 'heroku_token' => heroku_token, + 'github_token' => github_token + } + + service(:deployment, data, deployment_payload) + end + + def test_unsupported_push_events + data = { 'name' => 'my-app' } + exception = assert_raises(Service::ConfigurationError) do + service(:push, data, push_payload).receive_event + end + + message = "The push event is currently unsupported." + assert_equal message, exception.message + end + + def test_unsupported_status_events + data = { 'name' => 'my-app' } + exception = assert_raises(Service::ConfigurationError) do + service(:status, data, push_payload).receive_event + end + + message = "The status event is currently unsupported." + assert_equal message, exception.message + end + + def test_deployment_configured_properly + stub_heroku_access + stub_github_access + + @stubs.get "/repos/atmos/my-robot/tarball/9be5c2b9" do |env| + [302, {'Location' => 'https://git.io/a'}, ''] + end + + heroku_post_body = { + "source_blob" => { + "url" => "https://git.io/a", + "version" => "master@9be5c2b9" + } + } + + heroku_build_id = SecureRandom.uuid + heroku_build_path = "/apps/my-app/activity/builds/#{heroku_build_id}" + heroku_build_url = "https://dashboard.heroku.com#{heroku_build_path}" + + @stubs.post "/apps/my-app/builds" do |env| + assert_equal 'api.heroku.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal heroku_post_body, JSON.parse(env[:body]) + [200, {}, JSON.dump({'id' => heroku_build_id}) ] + end + + github_post_body = { + "state" => "success", + "target_url" => heroku_build_url, + "description" => "Created by GitHub Services@#{Service.current_sha[0..7]}" + } + + github_deployment_path = "/repos/atmos/my-robot/deployments/721/statuses" + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [200, {}, ''] + end + + heroku_service.receive_event + @stubs.verify_stubbed_calls + end + + def test_deployment_misconfigured + stub_heroku_access + stub_github_access + + @stubs.get "/repos/atmos/my-robot/tarball/9be5c2b9" do |env| + [302, {'Location' => 'https://git.io/a'}, ''] + end + + heroku_post_body = { + "source_blob" => { + "url" => "https://git.io/a", + "version" => "master@9be5c2b9" + } + } + + heroku_build_id = SecureRandom.uuid + heroku_build_path = "/apps/my-app/activity/builds/#{heroku_build_id}" + heroku_build_url = "https://dashboard.heroku.com#{heroku_build_path}" + + @stubs.post "/apps/my-app/builds" do |env| + assert_equal 'api.heroku.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal heroku_post_body, JSON.parse(env[:body]) + [200, {}, JSON.dump({'id' => heroku_build_id}) ] + end + + github_post_body = { + "state" => "success", + "target_url" => heroku_build_url, + "description" => "Created by GitHub Services@#{Service.current_sha[0..7]}" + } + + github_deployment_path = "/repos/atmos/my-robot/deployments/721/statuses" + @stubs.post github_deployment_path do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal github_post_body, JSON.parse(env[:body]) + [404, {}, ''] + end + + exception = assert_raises(Service::ConfigurationError) do + heroku_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to update the deployment status on GitHub with the provided token." + assert_equal message, exception.message + end + + def test_deployment_heroku_misconfigured + stub_heroku_access(404) + + exception = assert_raises(Service::ConfigurationError) do + heroku_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to access my-app on heroku with the provided token." + assert_equal message, exception.message + end + + def test_deployment_with_bad_github_user_credentials + stub_heroku_access + stub_github_user(404) + + exception = assert_raises(Service::ConfigurationError) do + heroku_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to access GitHub with the provided token." + assert_equal message, exception.message + end + + def test_deployment_without_access_to_github_repo + stub_heroku_access + stub_github_access(404) + + exception = assert_raises(Service::ConfigurationError) do + heroku_service.receive_event + end + @stubs.verify_stubbed_calls + + message = "Unable to access the atmos/my-robot repository on GitHub with the provided token." + assert_equal message, exception.message + end + + def service_class + Service::HerokuBeta + end + + def stub_github_user(code = 200) + @stubs.get "/user" do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + [code, {}, ''] + end + end + + def stub_github_access(code = 200, scopes = "repo, gist, user") + stub_github_user + @stubs.get "/repos/atmos/my-robot" do |env| + assert_equal 'api.github.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal "token #{github_token}", env[:request_headers]['Authorization'] + headers = {"X-OAuth-Scopes" => scopes } + [code, headers, ''] + end + end + + def stub_heroku_access(code = 200) + @stubs.get "/apps/my-app" do |env| + assert_equal 'api.heroku.com', env[:url].host + assert_equal 'https', env[:url].scheme + assert_equal Base64.encode64(":#{heroku_token}").strip, env[:request_headers]['Authorization'] + [code, {}, ''] + end + end + + def heroku_token + @heroku_token ||= SecureRandom.hex(24) + end + + def github_token + @github_token ||= SecureRandom.hex(24) + end +end diff --git a/test/hip_chat_test.rb b/test/hip_chat_test.rb index eca39186b..6a95a793d 100644 --- a/test/hip_chat_test.rb +++ b/test/hip_chat_test.rb @@ -7,19 +7,136 @@ def setup def test_push @stubs.post "/v1/webhooks/github" do |env| - assert_match /(^|\&)payload=%7B%22a%22%3A1%7D($|\&)/, env[:body] - assert_match "auth_token=a", env[:body] - assert_match "room_id=r", env[:body] + form = Faraday::Utils.parse_query(env[:body]) + assert_equal simple_payload, JSON.parse(form['payload']) + assert_equal 'a', form['auth_token'] + assert_equal 'r', form['room_id'] + assert_equal nil, form['color'] [200, {}, ''] end - svc = service( - {'auth_token' => 'a', 'room' => 'r'}, 'a' => 1) + svc = service({'auth_token' => 'a', 'room' => 'r'}, simple_payload) svc.receive_event end + def test_push_different_color + @stubs.post "/v1/webhooks/github" do |env| + form = Faraday::Utils.parse_query(env[:body]) + assert_equal 'purple', form['color'] + [200, {}, ''] + end + + params = {'auth_token' => 'a', 'room' => 'r', 'color' => 'purple'} + svc = service(params, simple_payload) + svc.receive_event + end + + def test_quiet_fork_silences_fork_events + [:fork, :fork_apply].each do |fork_event| + svc = service(fork_event, + default_data_plus('quiet_fork' => '1'), simple_payload ) + assert_nothing_raised { svc.receive_event } + end + end + + def test_quiet_fork_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_fork' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_quiet_watch_silences_watch_events + svc = service(:watch, + default_data_plus('quiet_watch' => '1'), simple_payload) + assert_nothing_raised { svc.receive_event } + end + + def test_quiet_watch_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_watch' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_quiet_comments_silences_comment_events + [:commit_comment, :issue_comment].each do |comment_event| + svc = service(comment_event, + default_data_plus('quiet_comments' => '1'), simple_payload ) + assert_nothing_raised { svc.receive_event } + end + end + + def test_quiet_comments_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_comments' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_quiet_labels_silences_comment_events + [:issue, :pull_request].each do |label_event| + svc = service(label_event, + default_data_plus('quiet_labels' => '1'), actionable_payload('labeled') ) + assert_nothing_raised { svc.receive_event } + end + end + + def test_quiet_labels_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_labels' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_quiet_assigned_silences_comment_events + [:issue, :pull_request].each do |assigned_event| + svc = service(assigned_event, + default_data_plus('quiet_assigning' => '1'), actionable_payload('assigned') ) + assert_nothing_raised { svc.receive_event } + end + end + + def test_quiet_assigned_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_assigning' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + + def test_quiet_wiki_silences_wiki_events + svc = service(:gollum, + default_data_plus('quiet_wiki' => '1'), simple_payload ) + assert_nothing_raised { svc.receive_event } + end + + def test_quiet_wiki_will_not_silence_other_events + stub_simple_post + svc = service(default_data_plus('quiet_wiki' => '1'), simple_payload) + svc.receive_event + @stubs.verify_stubbed_calls + end + def service(*args) super Service::HipChat, *args end + + def simple_payload + {'a' => 1, 'ref' => 'refs/heads/master'} + end + + def actionable_payload(action = 'labeled') + {'a' => 1, 'ref' => 'refs/heads/master', 'action' => action} + end + + def stub_simple_post + @stubs.post "/v1/webhooks/github" do |env| + [200, {}, ''] + end + end + + def default_data_plus(new_data) + {'auth_token' => 'a', 'room' => 'r'}.merge(new_data) + end end diff --git a/test/hostedgraphite.rb b/test/hostedgraphite.rb new file mode 100644 index 000000000..a97373422 --- /dev/null +++ b/test/hostedgraphite.rb @@ -0,0 +1,24 @@ +require File.expand_path('../helper', __FILE__) + +class HostedGraphiteTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + url = "/integrations/github/" + @stubs.post url do |env| + params = Faraday::Utils.parse_query env[:body] + assert_equal 'payload', JSON.parse(params['payload']) + assert_equal 'test', params['api_key'] + [200, {}, ''] + end + + svc = service :push, {'api_key' => 'test'}, 'payload' + svc.receive + end + + def service(*args) + super Service::Hostedgraphite, *args + end +end diff --git a/test/http_post_test.rb b/test/http_post_test.rb new file mode 100644 index 000000000..9dcaed078 --- /dev/null +++ b/test/http_post_test.rb @@ -0,0 +1,118 @@ +require File.expand_path('../helper', __FILE__) + +class HttpPostTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + svc = service(data={ + 'url' => 'http://monkey:secret@abc.com/foo/?a=1', + 'secret' => '' + }, payload) + + @stubs.post "/foo/" do |env| + assert_equal 'Basic bW9ua2V5OnNlY3JldA==', env[:request_headers]['authorization'] + assert_match /json/, env[:request_headers]['content-type'] + assert_equal 'abc.com', env[:url].host + assert_nil env[:url].port + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'a' => '1'}, params) + + body = JSON.parse(env[:body]) + assert_equal data, body['config'] + assert_equal 'push', body['event'] + assert_equal payload, body['payload'] + + assert_nil env[:request_headers]['X-Hub-Signature'] + [200, {}, ''] + end + + svc.receive_event + end + + def test_push_with_ssl + svc = service(data={ + 'url' => 'https://abc.com/foo', + 'secret' => '' + }, payload) + + @stubs.post "/foo/" do |env| + assert_equal 'abc.com', env[:url].host + assert_nil env[:url].port + end + end + + def test_push_without_scheme + svc = service({ + 'url' => 'abc.com/foo/?a=1', + 'secret' => '' + }, payload) + + @stubs.post "/foo/" do |env| + assert_equal 'abc.com', env[:url].host + [200, {}, ''] + end + + svc.receive_event + end + + def test_push_as_json + svc = service({ + 'url' => 'http://monkey:secret@abc.com/foo?a=1', + 'content_type' => 'json' + }, payload) + + @stubs.post "/foo" do |env| + assert_equal 'Basic bW9ua2V5OnNlY3JldA==', env[:request_headers]['authorization'] + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'a' => '1'}, params) + assert_match /json/, env[:request_headers]['content-type'] + assert_equal 'abc.com', env[:url].host + assert_nil env[:request_headers]['X-Hub-Signature'] + + body = JSON.parse(env[:body]) + assert_equal payload, body['payload'] + [200, {}, ''] + end + + svc.receive_event + end + + def test_push_as_json_with_secret + svc = service({ + 'url' => 'http://abc.com/foo', + 'secret' => 'monkey', + 'content_type' => 'json' + }, payload) + + @stubs.post "/foo" do |env| + assert_nil env[:request_headers]['authorization'] + assert_match /json/, env[:request_headers]['content-type'] + assert_equal 'abc.com', env[:url].host + assert_equal 'sha1='+OpenSSL::HMAC.hexdigest(Service::Web::HMAC_DIGEST, + 'monkey', env[:body]), + env[:request_headers]['X-Hub-Signature'] + + body = JSON.parse(env[:body]) + assert_equal payload, body['payload'] + [200, {}, ''] + end + + svc.receive_event + end + + def test_log_message + data = { + 'url' => 'http://abc.com/def', + 'secret' => 'monkey', + 'content_type' => 'json' + } + + svc = service(data, payload) + assert_match /^\[[^\]]+\] 200 #{service_class.hook_name}\/push \{/, svc.log_message(200) + end + + def service_class + Service::HttpPost + end +end + diff --git a/test/huboard_test.rb b/test/huboard_test.rb new file mode 100644 index 000000000..3e84aa9eb --- /dev/null +++ b/test/huboard_test.rb @@ -0,0 +1,34 @@ +require File.expand_path('../helper', __FILE__) + +class HuBoardTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_issues + @stubs.post "api/site/webhook/issue" do |env| + assert_equal 'huboard.com', env[:url].host + assert_equal 'application/x-www-form-urlencoded', + env[:request_headers]['content-type'] + [200, {}, ''] + end + + service(issues_payload).receive_issues + end + + def test_comment + @stubs.post "api/site/webhook/comment" do |env| + assert_equal 'huboard.com', env[:url].host + assert_equal 'application/x-www-form-urlencoded', + env[:request_headers]['content-type'] + [200, {}, ''] + end + + service(issue_comment_payload).receive_issue_comment + end + + def service(payload) + super Service::HuBoard, {}, payload + end +end + diff --git a/test/humbug_test.rb b/test/humbug_test.rb new file mode 100644 index 000000000..a3dae105a --- /dev/null +++ b/test/humbug_test.rb @@ -0,0 +1,45 @@ +require File.expand_path('../helper', __FILE__) + +class HumbugTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def post_checker(event) + return lambda { |env| + assert_equal "https", env[:url].scheme + assert_equal "api.zulip.com", env[:url].host + assert_match "payload=%7B%22test%22%3A%22payload%22%7D", env[:body] + assert_match "email=e", env[:body] + assert_match "api_key=a", env[:body] + assert_match "event=" + event, env[:body] + assert_match "stream=commits", env[:body] + assert_match "branches=b1%2Cb2", env[:body] + return [200, {}, ''] } + end + + def test_push + checker = post_checker "push" + @stubs.post "/v1/external/github", &checker + + svc = service(:push, + {'email' => 'e', 'api_key' => 'a', 'stream' => 'commits', 'branches' => 'b1,b2'}, + {'test' => 'payload'}) + svc.receive_event + end + + def test_pull_request + checker = post_checker "pull_request" + @stubs.post "/v1/external/github", &checker + + svc = service(:pull_request, + {'email' => 'e', 'api_key' => 'a', 'stream' => 'commits', 'branches' => 'b1,b2'}, + {'test' => 'payload'}) + svc.receive_event + end + + def service(*args) + super Service::Humbug, *args + end +end + diff --git a/test/ibm_devops_test.rb b/test/ibm_devops_test.rb new file mode 100644 index 000000000..b6b3f62fb --- /dev/null +++ b/test/ibm_devops_test.rb @@ -0,0 +1,73 @@ +require File.expand_path('../helper', __FILE__) + +class IBMDevOpsServicesTest < Service::TestCase + def setup + @stubs= Faraday::Adapter::Test::Stubs.new + @Pushes= 0 + end + + def test_push + svc = service( + {'ibm_id' => username, + 'ibm_password' => password}, + payload) + + @stubs.post "/manage/processGitHubPayload" do |env| + assert_equal 'hub.jazz.net', env[:url].host + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'auth_type' => 'ibmlogin'}, params) + @Pushes += 1 + [200, {}, ''] + end + svc.receive_push + assert_equal 1, @Pushes + end + + def test_push_empty_server_override + svc = service( + {'ibm_id' => username, + 'ibm_password' => password, + 'override_server_url' => ""}, + payload) + + @stubs.post "/manage/processGitHubPayload" do |env| + assert_equal 'hub.jazz.net', env[:url].host + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'auth_type' => 'ibmlogin'}, params) + @Pushes += 1 + [200, {}, ''] + end + svc.receive_push + assert_equal 1, @Pushes + end + + def test_push_server_override + svc = service( + {'ibm_id' => username, + 'ibm_password' => password, + 'override_server_url' => "https://test.example.org/foo"}, + payload) + + @stubs.post "/foo/processGitHubPayload" do |env| + assert_equal 'test.example.org', env[:url].host + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'auth_type' => 'ibmlogin'}, params) + @Pushes += 1 + [200, {}, ''] + end + svc.receive_push + assert_equal 1, @Pushes + end + + def username + return 'test_user' + end + + def password + return 'test_pass' + end + + def service(*args) + super Service::IBMDevOpsServices, *args + end +end diff --git a/test/icescrum_test.rb b/test/icescrum_test.rb new file mode 100644 index 000000000..c1b93806f --- /dev/null +++ b/test/icescrum_test.rb @@ -0,0 +1,127 @@ +require File.expand_path('../helper', __FILE__) + +class IceScrumTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push_valid + @stubs.post "/a/ws/p/TESTPROJ/commit" do |env| + assert_equal 'www.kagilum.com', env[:url].host + assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] + body = Faraday::Utils.parse_nested_query(env[:body]) + recv = JSON.parse(body['payload']) + assert_equal payload, recv + [200, {}, ''] + end + + svc = service({ + 'username' => 'u', + 'password' => 'p', + 'project_key' => 'TESTPROJ' + }, payload) + + svc.receive_push + @stubs.verify_stubbed_calls + end + + def test_push_valid_custom_url + @stubs.post "/icescrum/ws/p/TESTPROJ/commit" do |env| + assert_equal 'www.example.com', env[:url].host + assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] + body = Faraday::Utils.parse_nested_query(env[:body]) + recv = JSON.parse(body['payload']) + assert_equal payload, recv + [200, {}, ''] + end + + svc = service({ + 'username' => 'u', + 'password' => 'p', + 'project_key' => 'TESTPROJ', + 'base_url' => 'http://www.example.com/icescrum' + }, payload) + + svc.receive_push + @stubs.verify_stubbed_calls + end + + def test_push_lowcase_project_key + @stubs.post "/a/ws/p/TESTPROJ/commit" do |env| + assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] + body = Faraday::Utils.parse_nested_query(env[:body]) + recv = JSON.parse(body['payload']) + assert_equal payload, recv + [200, {}, ''] + end + + svc = service({ + 'username' => 'u', + 'password' => 'p', + 'project_key' => 'testProj' + }, payload) + + svc.receive_push + @stubs.verify_stubbed_calls + end + +def test_push_whitespace_project_key + @stubs.post "/a/ws/p/TESTPROJ/commit" do |env| + assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] + body = Faraday::Utils.parse_nested_query(env[:body]) + recv = JSON.parse(body['payload']) + assert_equal payload, recv + [200, {}, ''] + end + + svc = service({ + 'username' => ' u ', + 'password' => ' p ', + 'project_key' => ' TEST PROJ ' + }, payload) + + svc.receive_push + @stubs.verify_stubbed_calls + end + + def test_push_missing_username + svc = service({ + 'password' => 'p', + 'project_key' => 'TESTPROJ' + }, payload) + + assert_raises Service::ConfigurationError do + svc.receive_push + end + end + + def test_push_missing_password + svc = service({ + 'username' => 'u', + 'project_key' => 'TESTPROJ' + }, payload) + + assert_raises Service::ConfigurationError do + svc.receive_push + end + end + + def test_push_missing_project_key + svc = service({ + 'username' => 'u', + 'password' => 'p', + }, payload) + + assert_raises Service::ConfigurationError do + svc.receive_push + end + end + + def service(*args) + super Service::IceScrum, *args + end +end + + + + diff --git a/test/irc_test.rb b/test/irc_test.rb index e2f794530..cd86bb88f 100644 --- a/test/irc_test.rb +++ b/test/irc_test.rb @@ -3,23 +3,16 @@ class IRCTest < Service::TestCase class FakeIRC < Service::IRC - def readable_io - @readable_io ||= StringIO.new(" 004 n ") + def readable_irc + nick = data['nick'] + @readable_irc ||= StringIO.new(" 004 #{nick} \r\n:NickServ!nickserv@network.net PRIVMSG #{nick} :Successfully authenticated as #{nick}.\r\n") end - def writable_io - @writable_io ||= StringIO.new + def writable_irc + @writable_irc ||= StringIO.new end - def puts(*args) - writable_io.puts *args - end - - def gets - readable_io.gets - end - - def eof? + def irc_eof? true end @@ -29,12 +22,107 @@ def shorten_url(*args) end def test_push + expected = [ + "NICK n", + "USER n", + "JOIN #r", + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + "PART #r", + "QUIT" + ] + svc = service({'room' => 'r', 'nick' => 'n'}, payload) svc.receive_push - msgs = svc.writable_io.string.split("\n") + assert_irc_commands expected, svc.writable_irc.string + assert_equal 1, svc.remote_calls.size + + svc.remote_calls.each do |text| + incoming, outgoing = split_irc_debug(text) + modified = expected.dup + modified.unshift 'IRC Log:' + + assert_irc_commands ['004 n'], incoming + assert_irc_commands modified, outgoing + end + end + + def test_push_with_password + expected = [ + "PASS pass", + "NICK n", + "USER n", + "JOIN #r", + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + "PART #r", + "QUIT" + ] + + svc = service({'room' => 'r', 'nick' => 'n', 'password' => 'pass'}, payload) + + svc.receive_push + assert_irc_commands expected, svc.writable_irc.string + assert_equal 1, svc.remote_calls.size + + svc.remote_calls.each do |text| + incoming, outgoing = split_irc_debug(text) + censored = expected.dup + censored[0] = 'PASS ****' + censored.unshift 'IRC Log:' + + assert_irc_commands ['004 n'], incoming + assert_irc_commands censored, outgoing + end + end + + def test_push_with_nickserv + expected = [ + "NICK n", + "USER n", + "PRIVMSG NICKSERV :IDENTIFY pass", + "JOIN #r", + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + /PRIVMSG #r.*grit/, + "PART #r", + "QUIT" + ] + expected_incoming = [ + '004 n', + ':NickServ!nickserv@network.net PRIVMSG n :Successfully authenticated as n.' + ] + + svc = service({'room' => 'r', 'nick' => 'n', 'nickserv_password' => 'pass'}, payload) + + svc.receive_push + assert_irc_commands expected, svc.writable_irc.string + assert_equal 1, svc.remote_calls.size + + svc.remote_calls.each do |text| + incoming, outgoing = split_irc_debug(text) + censored = expected.dup + censored[2] = "PRIVMSG NICKSERV :IDENTIFY ****" + censored.unshift 'IRC Log:' + + assert_irc_commands expected_incoming, incoming + assert_irc_commands censored, outgoing + end + end + + def test_push_with_empty_branches + svc = service({'room' => 'r', 'nick' => 'n', 'branches' => ''}, payload) + + svc.receive_push + msgs = svc.writable_irc.string.split("\n") assert_equal "NICK n", msgs.shift - assert_match "USER n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift assert_equal "JOIN #r", msgs.shift.strip assert_match /PRIVMSG #r.*grit/, msgs.shift assert_match /PRIVMSG #r.*grit/, msgs.shift @@ -45,15 +133,13 @@ def test_push assert_nil msgs.shift end - def test_push_with_nickserv - svc = service({'room' => 'r', 'nick' => 'n', 'nickservidentify' => 'booya'}, - payload) + def test_push_with_single_matching_branches + svc = service({'room' => 'r', 'nick' => 'n', 'branches' => 'master'}, payload) svc.receive_push - msgs = svc.writable_io.string.split("\n") + msgs = svc.writable_irc.string.split("\n") assert_equal "NICK n", msgs.shift - assert_equal "MSG NICKSERV IDENTIFY booya", msgs.shift - assert_match "USER n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift assert_equal "JOIN #r", msgs.shift.strip assert_match /PRIVMSG #r.*grit/, msgs.shift assert_match /PRIVMSG #r.*grit/, msgs.shift @@ -64,13 +150,44 @@ def test_push_with_nickserv assert_nil msgs.shift end + def test_push_with_multiple_branches + svc = service({'room' => 'r', 'nick' => 'n', 'branches' => 'master,ticket'}, payload) + + svc.receive_push + msgs = svc.writable_irc.string.split("\n") + assert_equal "NICK n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift + assert_equal "JOIN #r", msgs.shift.strip + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_equal "PART #r", msgs.shift.strip + assert_equal "QUIT", msgs.shift.strip + assert_nil msgs.shift + end + + def test_commit_comment + svc = service(:commit_comment, {'room' => 'r', 'nick' => 'n'}, commit_comment_payload) + + svc.receive_commit_comment + msgs = svc.writable_irc.string.split("\n") + assert_equal "NICK n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift + assert_equal "JOIN #r", msgs.shift.strip + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_equal "PART #r", msgs.shift.strip + assert_equal "QUIT", msgs.shift.strip + assert_nil msgs.shift + end + def test_pull_request svc = service(:pull_request, {'room' => 'r', 'nick' => 'n'}, pull_payload) svc.receive_pull_request - msgs = svc.writable_io.string.split("\n") + msgs = svc.writable_irc.string.split("\n") assert_equal "NICK n", msgs.shift - assert_match "USER n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift assert_equal "JOIN #r", msgs.shift.strip assert_match /PRIVMSG #r.*grit/, msgs.shift assert_equal "PART #r", msgs.shift.strip @@ -82,9 +199,9 @@ def test_issues svc = service(:issues, {'room' => 'r', 'nick' => 'n'}, issues_payload) svc.receive_issues - msgs = svc.writable_io.string.split("\n") + msgs = svc.writable_irc.string.split("\n") assert_equal "NICK n", msgs.shift - assert_match "USER n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift assert_equal "JOIN #r", msgs.shift.strip assert_match /PRIVMSG #r.*grit/, msgs.shift assert_equal "PART #r", msgs.shift.strip @@ -92,8 +209,136 @@ def test_issues assert_nil msgs.shift end + def test_issue_comment + svc = service(:issue_comment, {'room' => 'r', 'nick' => 'n'}, issue_comment_payload) + + svc.receive_issue_comment + msgs = svc.writable_irc.string.split("\n") + assert_equal "NICK n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift + assert_equal "JOIN #r", msgs.shift.strip + assert_match /PRIVMSG #r.*grit/, msgs.shift + assert_equal "PART #r", msgs.shift.strip + assert_equal "QUIT", msgs.shift.strip + assert_nil msgs.shift + end + + def test_pull_request_review_comment + svc = service(:pull_request_review_comment, {'room' => 'r', 'nick' => 'n'}, pull_request_review_comment_payload) + + svc.receive_pull_request_review_comment + msgs = svc.writable_irc.string.split("\n") + assert_equal "NICK n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift + assert_equal "JOIN #r", msgs.shift.strip + assert_match /PRIVMSG #r.*grit.*pull request #5 /, msgs.shift + assert_equal "PART #r", msgs.shift.strip + assert_equal "QUIT", msgs.shift.strip + assert_nil msgs.shift + end + + def test_gollum + svc = service(:gollum, {'room' => 'r', 'nick' => 'n'}, gollum_payload) + + svc.receive_gollum + msgs = svc.writable_irc.string.split("\n") + assert_equal "NICK n", msgs.shift + assert_match /USER n . . :[^-]+- \w+(\/\w+)?/, msgs.shift + assert_equal "JOIN #r", msgs.shift.strip + assert_match /PRIVMSG #r.*\[grit\] defunkt created wiki page Foo/, msgs.shift + assert_equal "PART #r", msgs.shift.strip + assert_equal "QUIT", msgs.shift.strip + assert_nil msgs.shift + end + + def test_default_port_with_ssl + svc = service({'ssl' => '1'}, payload) + assert_equal 6697, svc.port + end + + def test_default_port_no_ssl + svc = service({'ssl' => '0'}, payload) + assert_equal 6667, svc.port + end + + def test_default_port_with_empty_string + svc = service({'port' => ''}, payload) + assert_equal 6667, svc.port + end + + def test_overridden_port + svc = service({'port' => '1234'}, payload) + assert_equal 1234, svc.port + end + + def test_no_colors + # Default should include color + svc = service(:pull_request, {'room' => 'r', 'nick' => 'n'}, pull_payload) + + svc.receive_pull_request + msgs = svc.writable_irc.string.split("\n") + privmsg = msgs[3] # skip NICK, USER, JOIN + assert_match /PRIVMSG #r.*grit/, privmsg + assert_match /\003/, privmsg + + # no_colors should strip color + svc = service(:pull_request, {'room' => 'r', 'nick' => 'n', 'no_colors' => '1'}, pull_payload) + + svc.receive_pull_request + msgs = svc.writable_irc.string.split("\n") + privmsg = msgs[3] # skip NICK, USER, JOIN + assert_match /PRIVMSG #r.*grit/, privmsg + refute_match /\003/, privmsg + end + + def test_public_repo_format_in_irc_realname + svc = service({'room' => 'r', 'nick' => 'n'}, payload) + + svc.receive_push + msgs = svc.writable_irc.string.split("\n") + + assert_includes msgs, "USER n 8 * :GitHub IRCBot - mojombo/grit" + end + + def test_private_repo_format_in_irc_realname + payload_copy = payload + payload_copy["repository"]["private"] = true + svc = service({'room' => 'r', 'nick' => 'n'}, payload_copy) + + svc.receive_push + msgs = svc.writable_irc.string.split("\n") + + assert_includes msgs, "USER n 8 * :GitHub IRCBot - mojombo" + end + + def test_nil_private_repo_format_in_irc_realname + payload_copy = payload + payload_copy["repository"]["private"] = nil + svc = service({'room' => 'r', 'nick' => 'n'}, payload_copy) + + svc.receive_push + msgs = svc.writable_irc.string.split("\n") + + assert_includes msgs, "USER n 8 * :GitHub IRCBot - mojombo" + end + def service(*args) super FakeIRC, *args end -end + def assert_irc_commands(expected, text) + lines = text.split("\n") + expected.each do |line| + assert_match line, lines.shift + end + assert_nil lines.shift + end + + def split_irc_debug(text) + all_lines = text.split("\n") + incoming, outgoing = all_lines.partition { |l| l =~ /^\=/ } + incoming.each { |s| s.sub!(/^\=\> /, '') } + outgoing.each { |s| s.sub!(/^\>\> /, '') } + [incoming.join("\n"), outgoing.join("\n")] + end +end diff --git a/test/irker_test.rb b/test/irker_test.rb new file mode 100644 index 000000000..1296c63cd --- /dev/null +++ b/test/irker_test.rb @@ -0,0 +1,96 @@ +require File.expand_path('../helper', __FILE__) +require 'json' + +class IrkerTest < Service::TestCase + class FakeIrkerd + def initialize + @messages = [] + end + def puts(message) + @messages << message + end + def messages + @messages + end + end + def setup + @server = FakeIrkerd.new + end + + def test_push + payload = { "repository" => "repository", "commits" => [{ "message" => "commitmsg", "author" => {"name" => "authorname"}, "id" => "8349815fed9", "modified" => ["foo", "bar", "baz"], "added" => [], "removed" => [] }] } + svc = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/##github-irker", 'project' => 'abc', 'long_url'=>1}, payload) + svc.irker_con = @server + svc.receive_push + + assert msg = @server.messages.shift + to_irker = JSON.parse(msg) + target = to_irker["to"] + if target.kind_of?(Array) then + assert_equal target.size, 1 + target = target[0] + end + assert_equal target, "irc://chat.freenode.net/##github-irker" + assert_match 'abc', to_irker["privmsg"] + assert_match 'authorname', to_irker["privmsg"] + assert_match '834981', to_irker["privmsg"] + assert_match 'commitmsg', to_irker["privmsg"] + end + + def test_channels + payload = { "repository" => "repository", "commits" => [{ "message" => "commitmsg", "author" => {"name" => "authorname"}, "id" => "8349815fed9", "modified" => ["foo", "bar", "baz"], "added" => [], "removed" => [] }] } + svc = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/#commits;irc://chat.freenode.net/#irker;irc://chat.freenode.net/testuser,isnick", 'project' => 'abc', 'long_url' => 1}, payload) + svc.irker_con = @server + svc.receive_push + + assert msg = @server.messages.shift + to_irker = JSON.parse(msg) + assert_equal to_irker["to"].sort, ["irc://chat.freenode.net/#commits", "irc://chat.freenode.net/#irker", "irc://chat.freenode.net/testuser,isnick"].sort + end + + def test_multiline + payload = { "repository" => "repository", "commits" => [{ "message" => "very\nlong\nmessage", "author" => {"name" => "authorname"}, "id" => "8349815fed9", "modified" => ["foo", "bar", "baz"], "added" => [], "removed" => [] }] } + svc_short = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/##github-irker", 'project' => 'abc', 'long_url'=>1, 'full_commits'=>0}, payload) + svc_long = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/##github-irker", 'project' => 'abc', 'long_url'=>1, 'full_commits'=>1}, payload) + svc_long.irker_con = svc_short.irker_con = @server + + svc_short.receive_push + assert_equal @server.messages.size, 1 + @server.messages.shift + assert_equal @server.messages.size, 0 + + svc_long.receive_push + assert_equal @server.messages.size, 1 + assert msg = @server.messages.shift + to_irker = JSON.parse(msg) + assert_equal to_irker["privmsg"].scan("\n").size, 3 + end + + def test_duplicates + payload = { "repository" => "repository", "commits" => [{ "message" => "msg", "author" => {"name" => "authorname"}, "id" => "3ef829ad", "modified" => ["foo", "bar"], "added" => ["foo"], "removed" => ["bar"] }] } + svc = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/#commits", 'project' => 'abc', 'long_url' => 1}, payload) + svc.irker_con = @server + svc.receive_push + + assert msg = @server.messages.shift + to_irker = JSON.parse(msg) + assert_equal to_irker["privmsg"].scan("foo").size, 1 + assert_equal to_irker["privmsg"].scan("bar").size, 1 + end + + def test_file_consolidation + payload = { "repository" => "repository", "commits" => [{ "message" => "commitmsg", "author" => {"name" => "authorname"}, "id" => "8349815fed9", "modified" => ["foo/a/bar/baz/andsomemore/filenumberone.hpp", "foo/a/bar/baz/andsomemore/filenumbertwo.hpp", "foo/b/quuuuuuuuuuuux/filenumberthree.cpp", "foo/b/quuuuuuuuuuuux/filenumberfour.cpp", "foo/b/bar/baz/andsomemore/filenumberfive.cpp", "foo/b/bar/baz/andsomemore/filenumbersix.cpp", "foo/b/filenumberseven.cpp"], "added" => [], "removed" => [] }] } + svc = service({'address' => "localhost", "channels" => "irc://chat.freenode.net/#commits;irc://chat.freenode.net/#irker;irc://chat.freenode.net/testuser,isnick", 'project' => 'abc', 'long_url' => 1}, payload) + svc.irker_con = @server + svc.receive_push + + assert msg = @server.messages.shift + end + + def service(*args) + super Service::Irker, *args + end +end + + + diff --git a/test/iron_mq_test.rb b/test/iron_mq_test.rb new file mode 100644 index 000000000..22bf9279f --- /dev/null +++ b/test/iron_mq_test.rb @@ -0,0 +1,35 @@ +require File.expand_path('../helper', __FILE__) + +class IronMQTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/v1/webhooks/github" do |env| + form = Faraday::Utils.parse_query(env[:body]) + p form + assert_equal payload, JSON.parse(form['payload']) + assert_equal 't', form['token'] + assert_equal '123', form['project_id'] + [200, {}, ''] + end + + token = 'x' + project_id = '111122223333444455556666' + svc = service( + { + 'token' => token, + 'project_id' => project_id + }, + payload) + data, payload, resp = svc.receive_event + assert_equal token, data['token'] + assert_equal project_id, data['project_id'] + assert_equal 200, resp.code + end + + def service(*args) + super Service::IronMQ, *args + end +end diff --git a/test/iron_worker_test.rb b/test/iron_worker_test.rb new file mode 100644 index 000000000..dd3004b30 --- /dev/null +++ b/test/iron_worker_test.rb @@ -0,0 +1,38 @@ +require File.expand_path('../helper', __FILE__) + +class IronWorkerTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/v1/webhooks/github" do |env| + form = Rack::Utils.parse_query(env[:body]) + p form + assert_equal payload, JSON.parse(form['payload']) + assert_equal 't', form['token'] + assert_equal '123', form['project_id'] + [200, {}, ''] + end + + token = 'x' + project_id = '111122223333444455556666' + code_name = 'fake_code_name' + svc = service( + { + 'token' => token, + 'project_id' => project_id, + 'code_name' => code_name + }, + payload) + data, payload, resp = svc.receive_event + assert_equal token, data['token'] + assert_equal project_id, data['project_id'] + assert_equal code_name, data['code_name'] + assert_equal 200, resp.code + end + + def service(*args) + super Service::IronWorker, *args + end +end diff --git a/test/jabber_test.rb b/test/jabber_test.rb deleted file mode 100644 index e4cf4133a..000000000 --- a/test/jabber_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -require File.expand_path('../helper', __FILE__) -require 'stringio' - -class JabberTest < Service::TestCase - class FakeJabber - class Client - attr_reader :conference - def initialize(conference) - @conference = conference - end - def active?() true end - end - - attr_accessor :accept_subscriptions - attr_reader :delivered - - def initialize - @delivered = [] - end - - def deliver_deferred(*args) - @delivered << args - end - end - - def test_push - svc = service({'user' => 'a,b , c , b', 'muc' => 'e,f , g, f'}, payload) - svc.im = FakeJabber.new - svc.mucs['e'] = FakeJabber::Client.new('e') - svc.mucs['f'] = FakeJabber::Client.new('f') - svc.mucs['g'] = FakeJabber::Client.new('g') - svc.receive_push - - assert svc.im.accept_subscriptions - - assert msg = svc.im.delivered.shift - assert_equal 'a', msg[0] - assert_equal :chat, msg[2] - - assert msg = svc.im.delivered.shift - assert_equal 'b', msg[0] - assert_equal :chat, msg[2] - - assert msg = svc.im.delivered.shift - assert_equal 'c', msg[0] - assert_equal :chat, msg[2] - - #assert msg = svc.im.delivered.shift - #assert_equal 'e', msg[0] - #assert_equal :groupchat, msg[2] - - #assert msg = svc.im.delivered.shift - #assert_equal 'f', msg[0] - #assert_equal :groupchat, msg[2] - - #assert msg = svc.im.delivered.shift - #assert_equal 'g', msg[0] - #assert_equal :groupchat, msg[2] - - assert_nil svc.im.delivered.shift - end - - def service(*args) - super Service::Jabber, *args - end -end - - diff --git a/test/jenkins_git_test.rb b/test/jenkins_git_test.rb new file mode 100644 index 000000000..ae7d5e0f1 --- /dev/null +++ b/test/jenkins_git_test.rb @@ -0,0 +1,40 @@ +require File.expand_path('../helper', __FILE__) + +class JenkinsGitTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @options = {'jenkins_url' => 'http://monkey:secret@jenkins.example.com/jenkins/'} + end + + def test_push + @stubs.get "/jenkins/git/notifyCommit" do |env| + assert_equal 'jenkins.example.com', env[:url].host + + assert_equal 'Basic bW9ua2V5OnNlY3JldA==', env[:request_headers]['authorization'] + + params = Faraday::Utils.parse_nested_query(env[:url].query) + expected_params = { + 'url' => 'http://github.com/mojombo/grit', + 'branches' => 'master', + 'from' => 'github' + } + assert_equal(expected_params, params) + + [200, {}, ''] + end + + service(@options, payload).receive_push + + @stubs.verify_stubbed_calls + end + + def test_no_jenkins_hook_url + assert_raises Service::ConfigurationError do + service({'jenkins_url' => ''}, payload).receive_push + end + end + + def service(*args) + super Service::JenkinsGit, *args + end +end diff --git a/test/jenkins_github_test.rb b/test/jenkins_github_test.rb new file mode 100644 index 000000000..f959a79e1 --- /dev/null +++ b/test/jenkins_github_test.rb @@ -0,0 +1,34 @@ +require File.expand_path('../helper', __FILE__) + +class JenkinsGitHubTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/github-webhook/" do |env| + assert_equal 'jenkins.example.com', env[:url].host + assert_equal 'Basic bW9ua2V5OnNlY3JldA==', + env[:request_headers]['authorization'] + assert_equal 'application/x-www-form-urlencoded', + env[:request_headers]['content-type'] + [200, {}, ''] + end + + svc = service :push, + {'jenkins_hook_url' => 'http://monkey:secret@jenkins.example.com/github-webhook/'}, payload + svc.receive_push + end + + def test_no_jenkins_hook_url + assert_raises Service::ConfigurationError do + svc = service :push, + {'jenkins_hook_url' => ''}, payload + svc.receive_push + end + end + + def service(*args) + super Service::JenkinsGitHub, *args + end +end diff --git a/test/jira_test.rb b/test/jira_test.rb index 978c1da4f..676dbed16 100644 --- a/test/jira_test.rb +++ b/test/jira_test.rb @@ -1,6 +1,6 @@ require File.expand_path('../helper', __FILE__) -class JiraTest < Service::TestCase +class JIRATest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new end @@ -20,7 +20,6 @@ def test_push end def service(*args) - super Service::Jira, *args + super Service::JIRA, *args end end - diff --git a/test/kanbanize_test.rb b/test/kanbanize_test.rb new file mode 100644 index 000000000..0022895eb --- /dev/null +++ b/test/kanbanize_test.rb @@ -0,0 +1,65 @@ +require File.expand_path('../helper', __FILE__) + +class KanbanizeTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + url = '/index.php/api/kanbanize/git_hub_event' + @stubs.post url do |env| + assert_equal 'testdomain.kanbanize.com', env[:url].host + assert_equal '/index.php/api/kanbanize/git_hub_event', env[:url].request_uri + assert_equal %({"ref":"refs/heads/master"}), env[:body] + assert_equal 'a1b2c3==', env[:request_headers]['apikey'] + assert_equal '', env[:request_headers]['branch-filter'] + assert_equal false, env[:request_headers]['last-commit'] + assert_equal false, env[:request_headers]['track-issues'] + assert_equal '', env[:request_headers]['board-id'] + [200, {}, ''] + end + + svc = service({'kanbanize_domain_name' => 'testdomain.kanbanize.com', 'kanbanize_api_key' => 'a1b2c3=='}, {'ref' => 'refs/heads/master'}) + svc.receive_event + end + + def test_push_with_restrictions + url = '/index.php/api/kanbanize/git_hub_event' + @stubs.post url do |env| + assert_equal 'testdomain.kanbanize.com', env[:url].host + assert_equal '/index.php/api/kanbanize/git_hub_event', env[:url].request_uri + assert_equal %({"ref":"refs/heads/mybranch2"}), env[:body] + assert_equal 'a1b2c3==', env[:request_headers]['apikey'] + assert_equal 'mybranch1,mybranch2', env[:request_headers]['branch-filter'] + assert_equal true, env[:request_headers]['last-commit'] + assert_equal false, env[:request_headers]['track-issues'] + assert_equal '', env[:request_headers]['board-id'] + [200, {}, ''] + end + + svc = service({'kanbanize_domain_name' => 'testdomain.kanbanize.com', 'kanbanize_api_key' => 'a1b2c3==', 'restrict_to_branch' => 'mybranch1,mybranch2', 'restrict_to_last_commit' => '1'}, {'ref' => 'refs/heads/mybranch2'}) + svc.receive_event + end + + def test_push_with_issue_tracking + url = '/index.php/api/kanbanize/git_hub_event' + @stubs.post url do |env| + assert_equal 'testdomain.kanbanize.com', env[:url].host + assert_equal '/index.php/api/kanbanize/git_hub_event', env[:url].request_uri + assert_equal %({"action":"created"}), env[:body] + assert_equal 'a1b2c3==', env[:request_headers]['apikey'] + assert_equal '', env[:request_headers]['branch-filter'] + assert_equal false, env[:request_headers]['last-commit'] + assert_equal true, env[:request_headers]['track-issues'] + assert_equal '131', env[:request_headers]['board-id'] + [200, {}, ''] + end + + svc = service(:issues, {'kanbanize_domain_name' => 'testdomain.kanbanize.com', 'kanbanize_api_key' => 'a1b2c3==', 'track_project_issues_in_kanbanize' => '1', 'project_issues_board_id' => '131'}, {'action' => 'created'}) + svc.receive_event + end + + def service(*args) + super Service::Kanbanize, *args + end +end diff --git a/test/kickoff_test.rb b/test/kickoff_test.rb deleted file mode 100644 index 58663b745..000000000 --- a/test/kickoff_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class KickoffTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/projects/a/chat" do |env| - assert_equal 'api.kickoffapp.com', env[:url].host - assert_equal 'token=b', env[:url].query - [200, {}, ''] - end - - svc = service( - {'project_id' => 'a', 'project_token' => 'b' }, - payload) - svc.receive_push - end - - def service(*args) - super Service::Kickoff, *args - end -end - diff --git a/test/landscape_test.rb b/test/landscape_test.rb new file mode 100644 index 000000000..a4051718f --- /dev/null +++ b/test/landscape_test.rb @@ -0,0 +1,34 @@ +require File.expand_path('../helper', __FILE__) + +class LandscapeTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + data = {} + payload = { + 'commits'=>[{'id'=>'test'}], + 'repository'=>{'id'=>'repoid'} + } + + svc = service(data, payload) + + @stubs.post "/hooks/github" do |env| + body = JSON.parse(env[:body]) + + assert_equal env[:url].host, "landscape.io" + assert_equal 'test', body['payload']['commits'][0]['id'] + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + assert_equal 'repoid', body['payload']['repository']['id'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + def service_class + Service::Landscape + end +end + diff --git a/test/leanpub_test.rb b/test/leanpub_test.rb new file mode 100644 index 000000000..d2a336f30 --- /dev/null +++ b/test/leanpub_test.rb @@ -0,0 +1,33 @@ +require File.expand_path('../helper', __FILE__) + +class LeanpubTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + test_api_key = "123abc" + test_slug = "myamazingbook" + + data = { + 'api_key' => test_api_key, + 'slug' => test_slug + } + + payload = {} + svc = service(data, payload) + + @stubs.post "/#{test_slug}/preview?api_key=#{test_api_key}" do |env| + body = JSON.parse(env[:body]) + + assert_equal env[:url].host, "leanpub.com" + assert_equal 'push', body['event'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service_class + Service::Leanpub + end +end diff --git a/test/leanto_test.rb b/test/leanto_test.rb deleted file mode 100644 index a72875586..000000000 --- a/test/leanto_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class LeantoTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - url = "/api/abc/commit" - @stubs.post url do |env| - assert_equal "payload=%22payload%22", env[:body] - [200, {}, ''] - end - - svc = service :push, {'token' => 'abc'}, 'payload' - svc.receive - end - - def service(*args) - super Service::Leanto, *args - end -end diff --git a/test/lingohub_test.rb b/test/lingohub_test.rb new file mode 100644 index 000000000..04c767f04 --- /dev/null +++ b/test/lingohub_test.rb @@ -0,0 +1,41 @@ +require File.expand_path('../helper', __FILE__) + +class LingohubTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_http_call + @stubs.post "/github_callback" do |env| + assert_equal 'lingohub.com', env[:url].host + assert_equal 'a27f34', env[:params]['auth_token'] + + [200, {}, ''] + end + + svc = service({'project_token' => 'a27f34'}, :a => 1) + svc.receive_push + end + + def test_payload + @stubs.post "/github_callback" do |env| + + body = Faraday::Utils.parse_nested_query(env[:body]) + received_payload = JSON.parse(body['payload']) + + assert_equal payload['after'], received_payload['after'] + + [200, {}, ''] + end + + svc = service({'project_token' => 'a27f34'}, payload) + svc.receive_push + end + + + + def service(*args) + super Service::Lingohub, *args + end +end + diff --git a/test/loggly_test.rb b/test/loggly_test.rb deleted file mode 100644 index 2aafdbac7..000000000 --- a/test/loggly_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -class LogglyTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/inputs/input-foo" do |env| - assert_equal 'application/json', env[:request_headers]['Content-Type'] - assert_equal 'logs.loggly.com', env[:url].host - [200, {}, ''] - end - - svc = service( - {"input_token" => "input-foo"}, - payload) - svc.receive_push - end - - def service(*args) - super Service::Loggly, *args - end -end diff --git a/test/masterbranch_test.rb b/test/masterbranch_test.rb deleted file mode 100644 index 9a343f5e9..000000000 --- a/test/masterbranch_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class MasterbranchTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/gh-hook" do |env| - assert_equal 'webhooks.masterbranch.com', env[:url].host - assert_match 'payload=%7B%22a%22%3A1%7D', env[:body] - [200, {}, ''] - end - - svc = service({}, :a => 1) - svc.receive_push - end - - def service(*args) - super Service::Masterbranch, *args - end -end - diff --git a/test/maxcdn_test.rb b/test/maxcdn_test.rb new file mode 100644 index 000000000..fb81e5571 --- /dev/null +++ b/test/maxcdn_test.rb @@ -0,0 +1,108 @@ +require File.expand_path("../helper", __FILE__) + +# stub +require "maxcdn" +module MaxCDN + class Client + def initialize *args + @purge_calls = 0 + @fake_error = false + end + + def purge id + raise ::MaxCDN::APIException.new("test error") if @fake_error + + @purge_calls += 1 + return { "code" => "200" } + end + + def fake_error + @fake_error = true + end + end +end + +class MaxCDNTest < Service::TestCase + def setup + @arguments ||= { + "alias" => "foobar_alias", + "key" => "foobar_key", + "secret" => "foobar_secret", + "zone_id" => 123456, + "static_only" => '0' + } + + @svc = service(@arguments, dynamic_payload) + end + + def test_maxcdn + assert @svc.maxcdn + end + + def test_extensions + assert_includes @svc.extensions, :js + end + + def test_has_static? + refute @svc.has_static? + + svc = service(@arguments, static_payload) + assert svc.has_static? + end + + def test_receive_push + assert @svc.receive_push + assert_equal 1, @svc.maxcdn.instance_variable_get(:@purge_calls) + + @svc.maxcdn.fake_error + error = assert_raises ::Service::ConfigurationError do + @svc.receive_push + end + + assert_match /test error/, error.message + + arguments = @arguments.clone + arguments["static_only"] = '1' + svc = service(arguments, payload) + + refute svc.receive_push + + arguments = @arguments.clone + arguments["static_only"] = '1' + svc = service(arguments, static_payload) + + assert svc.receive_push + end + + def dynamic_payload + unless defined? @dynamic_payload + # Default payload is all .rb files and thus, a + # non-static payload. However, to be sure (should + # something change in the future) I'll ensure it. + @dynamic_payload = payload.clone + @dynamic_payload["commits"].each_index do |commit| + @dynamic_payload["commits"][commit]["modified"].each_index do |file| + @dynamic_payload["commits"][commit]["modified"][file].gsub!(/\.[a-z0-9]+$/, ".rb") + end + end + end + @dynamic_payload + end + + def static_payload + unless defined? @static_payload + # Creating a static payload, by replacing a single + # file in the payload with a static file extension. + @static_payload = payload.clone + @static_payload["commits"] + .first["modified"] + .last + .gsub!(/\.[a-z0-9]+$/, ".js") + end + @static_payload + end + + def service(*args) + super Service::MaxCDN, *args + end +end diff --git a/test/mqtt_test.rb b/test/mqtt_test.rb new file mode 100644 index 000000000..cf55cb244 --- /dev/null +++ b/test/mqtt_test.rb @@ -0,0 +1,17 @@ +require File.expand_path('../helper', __FILE__) + +class MqttPubTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + skip "Temporarily disabled as the q.m2m.io host is returning 503 errors" + svc = service :push, {'broker' => 'q.m2m.io','port' => '1883', 'id' => 'github', 'topic' => 'github/franklovecchio/github-services'}, 'payload' + svc.receive + end + + def service(*args) + super Service::MqttPub, *args + end +end diff --git a/test/myget_test.rb b/test/myget_test.rb new file mode 100644 index 000000000..440f1054a --- /dev/null +++ b/test/myget_test.rb @@ -0,0 +1,35 @@ +require File.expand_path('../helper', __FILE__) + +class MyGetTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + test_hook_url = "https://www.myget.org/BuildSource/Hook/feedname?identifier=guid" + test_hook_pathandquery = "/BuildSource/Hook/feedname?identifier=guid" + + data = { + 'hook_url' => test_hook_url + } + + payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) + + @stubs.post "#{test_hook_pathandquery}" do |env| + body = JSON.parse(env[:body]) + + assert_equal 'test', body['payload']['commits'][0]['id'] + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + [201, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service_class + Service::MyGet + end +end + diff --git a/test/notifo_test.rb b/test/notifo_test.rb deleted file mode 100644 index 2fa530383..000000000 --- a/test/notifo_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class NotifoTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - subscribed = %w(a b) - notified = %w(a b) - @stubs.post "/v1/subscribe_user" do |env| - assert_equal 'api.notifo.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) - assert_equal subscribed.shift, data['username'] - [200, {}, ''] - end - - @stubs.post "/v1/send_notification" do |env| - assert_equal 'api.notifo.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) - assert_equal notified.shift, data['to'] - [200, {}, ''] - end - - svc = service({'subscribers' => 'a,b'}, payload) - svc.secrets = {'notifo' => {'apikey' => 'a'}} - svc.receive_push - end - - def test_push_with_empty_commits - data = payload - data['commits'] = [] - - svc = service({'subscribers' => 'a,b'}, data) - svc.secrets = {'notifo' => {'apikey' => 'a'}} - svc.receive_push - end - - def service(*args) - super Service::Notifo, *args - end -end - diff --git a/test/notifymyandroid_test.rb b/test/notifymyandroid_test.rb deleted file mode 100644 index 727b6d5b2..000000000 --- a/test/notifymyandroid_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class NMATest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/publicapi/notify" do |env| - assert_equal 'www.notifymyandroid.com', env[:url].host - data = Rack::Utils.parse_query(env[:body]) - assert_equal 'a', data['apikey'] - [200, {}, ''] - end - - svc = service({'apikey' => 'a'}, payload) - svc.receive_push - end - - def service(*args) - super Service::NMA, *args - end -end - diff --git a/test/obs_test.rb b/test/obs_test.rb new file mode 100644 index 000000000..ecde8abc0 --- /dev/null +++ b/test/obs_test.rb @@ -0,0 +1,126 @@ +require File.expand_path('../helper', __FILE__) + +class ObsTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def single_token_data + { + # service works with all current OBS 2.5 instances + "url" => "http://api.opensuse.org:443", + "token" => "github/test/token/string", + # optional + "project" => "home:adrianSuSE", + "package" => "4github", + } + end + + def multi_token_data + { + # service works with all current OBS 2.5 instances + "url" => "http://api.opensuse.org:443", + "token" => "github/test/token/one, github/test/token/two", + # optional + "project" => "home:adrianSuSE", + "package" => "4github", + } + end + + def filter_data + { + "url" => "http://api.opensuse.org:443", + "token" => "github/test/token/string", + "project" => "home:adrianSuSE", + "package" => "4github", + "refs" => "refs/tags/version-*:refs/heads/production", + } + end + + def test_push_single_token + apicall = "/trigger/runservice" + @stubs.post apicall do |env| + assert_equal 'api.opensuse.org', env[:url].host + params = Faraday::Utils.parse_query env[:body] + assert_equal 'Token github/test/token/string', env[:request_headers]["Authorization"] + assert_equal '/trigger/runservice', env[:url].path + assert_equal 'package=4github&project=home%3AadrianSuSE', env[:url].query + [200, {}, ''] + end + + svc = service :push, single_token_data, payload + svc.receive + end + + def test_push_multi_token + apicall = "/trigger/runservice" + match = 0 + @stubs.post apicall do |env| + assert_equal 'api.opensuse.org', env[:url].host + params = Faraday::Utils.parse_query env[:body] + match=match+1 if ['Token github/test/token/one', 'Token github/test/token/two'].include? env[:request_headers]["Authorization"] + assert_equal '/trigger/runservice', env[:url].path + assert_equal 'package=4github&project=home%3AadrianSuSE', env[:url].query + [200, {}, ''] + end + + svc = service :push, multi_token_data, payload + svc.receive + # both tokens received + assert_equal match, 2 + end + + def test_filter_passed_by_tag + apicall = "/trigger/runservice" + @stubs.post apicall do |env| + assert_equal 'api.opensuse.org', env[:url].host + params = Faraday::Utils.parse_query env[:body] + assert_equal 'Token github/test/token/string', env[:request_headers]["Authorization"] + assert_equal '/trigger/runservice', env[:url].path + assert_equal 'package=4github&project=home%3AadrianSuSE', env[:url].query + [200, {}, ''] + end + + # Modify the payload to match the filter. + pay = payload + pay['ref'] = 'refs/tags/version-1.1' + + svc = service :push, filter_data, pay + assert svc.receive + @stubs.verify_stubbed_calls + end + + def test_filter_passed_by_branch + apicall = "/trigger/runservice" + @stubs.post apicall do |env| + assert_equal 'api.opensuse.org', env[:url].host + params = Faraday::Utils.parse_query env[:body] + assert_equal 'Token github/test/token/string', env[:request_headers]["Authorization"] + assert_equal '/trigger/runservice', env[:url].path + assert_equal 'package=4github&project=home%3AadrianSuSE', env[:url].query + [200, {}, ''] + end + + # Modify the payload to match the filter. + pay = payload + pay['ref'] = 'refs/heads/production' + + svc = service :push, filter_data, pay + svc.receive + @stubs.verify_stubbed_calls + end + + def test_filter_rejected + apicall = "/trigger/runservice" + @stubs.post apicall do |env| + flunk "Master branch should not trigger post request" + end + + svc = service :push, filter_data, payload + svc.receive + end + + def service(*args) + super Service::Obs, *args + end +end diff --git a/test/ontime_test.rb b/test/ontime_test.rb index 85d66cbcf..fecf4d8b2 100644 --- a/test/ontime_test.rb +++ b/test/ontime_test.rb @@ -1,3 +1,4 @@ +require File.expand_path('../helper', __FILE__) require 'cgi' class OnTimeTest < Service::TestCase @@ -5,6 +6,7 @@ def setup @stubs = Faraday::Adapter::Test::Stubs.new end + # This tests the old api paths of OnTime. Where 11.1.0 <= Version < 12.2.0 def test_push @stubs.get "/api/version" do |env| assert_equal 'www.example.com', env[:url].host @@ -19,6 +21,21 @@ def test_push svc.receive_push end + # This tests the new api path for GitHub in OnTime Version 12.2 and later. + def test_push_v1_api + @stubs.get "/v122/api/version" do |env| + assert_equal 'www.example.com', env[:url].host + [200, {}, '{"data":{"major":12,"minor":2,"build":0}}'] + end + + @stubs.post "/v122/api/v1/github" do |env| + [200, {}, ''] + end + + svc = service({'ontime_url' => 'http://www.example.com/v122', 'api_key' => 'test_v1_api_key'}, payload) + svc.receive_push + end + def service(*args) super Service::OnTime, *args end diff --git a/test/pachube_test.rb b/test/pachube_test.rb deleted file mode 100644 index 485fb433f..000000000 --- a/test/pachube_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class PachubeTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_require_api_key - svc = service({}, payload) - assert_raises Service::ConfigurationError do - svc.receive_push - end - end - - def test_require_feed_id - svc = service({'api_key' => 'abcd1234'}, payload) - assert_raises Service::ConfigurationError do - svc.receive_push - end - end - - def test_push - @stubs.put "/v2/feeds/1234.json" do |env| - assert_equal 'api.pachube.com', env[:url].host - assert_equal 'https', env[:url].scheme - parsed_body = JSON.parse(env[:body]) - assert_equal '1.0.0', parsed_body['version'] - assert_equal [{'current_value' => 3, 'id' => 'grit'}], parsed_body['datastreams'] - [200, {}, ''] - end - - svc = service({'api_key' => 'abcd1234', 'feed_id' => '1234'}, payload) - svc.receive_push - end - - def service(*args) - super Service::Pachube, *args - end -end - diff --git a/test/packagist_test.rb b/test/packagist_test.rb new file mode 100644 index 000000000..84c446078 --- /dev/null +++ b/test/packagist_test.rb @@ -0,0 +1,193 @@ +require File.expand_path('../helper', __FILE__) + +class PackagistTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @svc = service(data, payload) + end + + def test_reads_user_from_data + assert_equal 'simensen', @svc.user + end + + def test_reads_token_from_data + assert_equal "5gieo7lwcd8gww800scs", @svc.token + end + + def test_reads_domain_from_data + assert_equal "packagist.example.com", @svc.domain + end + + def test_keeps_https_scheme + svc = service(data.merge({'domain' => 'https://example.com'}), payload) + assert_equal 'https', svc.scheme + end + + def test_constructs_post_receive_url + assert_equal 'http://packagist.example.com/api/github', + @svc.packagist_url + end + + def test_posts_payload + @stubs.post '/api/github' do |env| + assert_equal 'packagist.example.com', env[:url].host + assert_equal 'simensen', Faraday::Utils.parse_query(env[:body])['username'] + assert_equal '5gieo7lwcd8gww800scs', Faraday::Utils.parse_query(env[:body])['apiToken'] + assert_equal payload, JSON.parse(Faraday::Utils.parse_query(env[:body])['payload']) + end + @svc.receive_push + end + + def test_strips_whitespace_from_form_values + data = { + 'user' => 'simensen ', + 'token' => '5gieo7lwcd8gww800scs ', + 'domain' => 'packagist.example.com ' + } + + svc = service(data, payload) + assert_equal 'simensen', svc.user + assert_equal '5gieo7lwcd8gww800scs', svc.token + assert_equal 'packagist.example.com', svc.domain + end + + def test_handles_blank_strings_without_errors + data = { + 'user' => '', + 'token' => '', + 'domain' => '' + } + + svc = service(data, payload) + assert_equal 'mojombo', svc.user + assert_equal '', svc.token + assert_equal 'packagist.org', svc.domain + assert_equal 'https', svc.scheme + end + + def test_detects_http_url + data = { + 'domain' => 'http://packagist.example.com/' + } + + svc = service(data, payload) + assert_equal 'packagist.example.com', svc.domain + assert_equal 'http', svc.scheme + end + + def test_detects_https_url + data = { + 'domain' => 'https://packagist.example.com/' + } + + svc = service(data, payload) + assert_equal 'packagist.example.com', svc.domain + assert_equal 'https', svc.scheme + end + + def test_strips_trailing_slash + data = { + 'domain' => 'packagist.example.com/ ' + } + + svc = service(data, payload) + assert_equal 'packagist.example.com', svc.domain + end + + def test_strips_trailing_slash_deep_path + data = { + 'domain' => 'packagist.example.com/path/to/subdirectory/ ' + } + + svc = service(data, payload) + assert_equal 'packagist.example.com/path/to/subdirectory', svc.domain + end + + def test_infers_user_from_repo_data + svc = service(data.reject{|key,v| key == 'user'}, payload) + assert_equal "mojombo", svc.user + end + + def test_defaults_to_http_scheme + assert_equal 'http', @svc.scheme + end + + def test_defaults_to_packagist_domain + svc = service(data.reject{|key,v| key == 'domain'}, payload) + assert_equal "packagist.org", svc.domain + end + + def test_packagist_forced_to_tls + data = { + 'domain' => 'http://packagist.org' + } + svc = service(data, payload) + assert_equal 'packagist.org', svc.domain + assert_equal 'https', svc.scheme + end + + def service(*args) + super Service::Packagist, *args + end + + def data + { + 'user' => 'simensen', + 'token' => '5gieo7lwcd8gww800scs', + 'domain' => 'packagist.example.com' + } + end + def payload2 + { + "after" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "ref" => "refs/heads/master", + "before" => "4c8124ffcf4039d292442eeccabdeca5af5c5017", + "compare" => "http://github.com/mojombo/grit/compare/4c8124ffcf4039d292442eeccabdeca5af5c5017...a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "forced" => false, + "created" => false, + "deleted" => false, + + "repository" => { + "name" => "grit", + "url" => "http://github.com/mojombo/grit", + "owner" => { "name" => "mojombo", "email" => "tom@mojombo.com" } + }, + + "commits" => [ + { + "distinct" => true, + "removed" => [], + "message" => "[#WEB-249 status:31 resolution:1] stub git call for Grit#heads test", + "added" => [], + "timestamp" => "2007-10-10T00:11:02-07:00", + "modified" => ["lib/grit/grit.rb", "test/helper.rb", "test/test_grit.rb"], + "url" => "http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325", + "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, + "id" => "06f63b43050935962f84fe54473a7c5de7977325" + }, + { + "distinct" => true, + "removed" => [], + "message" => "clean up heads test", + "added" => [], + "timestamp" => "2007-10-10T00:18:20-07:00", + "modified" => ["test/test_grit.rb"], + "url" => "http://github.com/mojombo/grit/commit/5057e76a11abd02e83b7d3d3171c4b68d9c88480", + "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, + "id" => "5057e76a11abd02e83b7d3d3171c4b68d9c88480" + }, + { + "distinct" => true, + "removed" => [], + "message" => "add more comments throughout", + "added" => [], + "timestamp" => "2007-10-10T00:50:39-07:00", + "modified" => ["lib/grit.rb", "lib/grit/commit.rb", "lib/grit/grit.rb"], + "url" => "http://github.com/mojombo/grit/commit/a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, + "id" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425" + } + ] + } + end +end diff --git a/test/phraseapp_test.rb b/test/phraseapp_test.rb new file mode 100644 index 000000000..a74a0cead --- /dev/null +++ b/test/phraseapp_test.rb @@ -0,0 +1,32 @@ +require File.expand_path('../helper', __FILE__) + +class PhraseappTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + test_auth_token = "footoken" + + data = { + "auth_token" => test_auth_token + } + + payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) + + @stubs.post "/api/v1/hooks/github" do |env| + body = JSON.parse(env[:body]) + + assert_equal("phraseapp.com", env[:url].host) + assert_equal("post", env[:method].to_s) + [200, {}, ''] + end + + svc.receive_push + @stubs.verify_stubbed_calls + end + + def service_class + Service::Phraseapp + end +end + diff --git a/test/piwik_plugins_test.rb b/test/piwik_plugins_test.rb new file mode 100644 index 000000000..a24bfde24 --- /dev/null +++ b/test/piwik_plugins_test.rb @@ -0,0 +1,25 @@ +require File.expand_path('../helper', __FILE__) + +class PiwikPluginsTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + svc = service({}, 'a' => 1) + + @stubs.post "/postreceive-hook" do |env| + body = JSON.parse(env[:body]) + assert_equal 'plugins.piwik.org', env[:url].host + assert_equal 1, body['payload']['a'] + [200, {}, ''] + end + + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def service_class + Service::PiwikPlugins + end +end + diff --git a/test/planbox_test.rb b/test/planbox_test.rb index 016455078..4984929be 100644 --- a/test/planbox_test.rb +++ b/test/planbox_test.rb @@ -7,7 +7,7 @@ def setup def test_push @stubs.post "/api/github_commits" do |env| - assert_equal 'www.planbox.com', env[:url].host + assert_equal 'work.planbox.com', env[:url].host assert_match 'payload=%7B%22a%22%3A1%7D', env[:body] [200, {}, ''] end diff --git a/test/planio_test.rb b/test/planio_test.rb new file mode 100644 index 000000000..e8197f6a2 --- /dev/null +++ b/test/planio_test.rb @@ -0,0 +1,26 @@ +require File.expand_path('../helper', __FILE__) + +class PlanioTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.get "/a/sys/fetch_changesets" do |env| + assert_equal 'r.com', env[:url].host + data = Faraday::Utils.parse_nested_query(env[:body]) + assert_equal 'a', env[:params]['key'] + assert_equal 'p', env[:params]['id'] + [200, {}, ''] + end + + svc = service({'address' => 'http://r.com/a/', + 'api_key' => 'a', 'project' => 'p'}, :a => 1) + svc.receive_push + end + + def service(*args) + super Service::Planio, *args + end +end + diff --git a/test/presently_test.rb b/test/presently_test.rb deleted file mode 100644 index 2f2d00ef1..000000000 --- a/test/presently_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class PresentlyTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/api/twitter/statuses/update.xml" do |env| - assert_equal 's.presently.com', env[:url].host - assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] - [200, {}, ''] - end - - svc = service({'subdomain' => 's', - 'username' => 'u', 'password' => 'p'}, payload) - svc.receive_push - end - - def service(*args) - super Service::Presently, *args - end -end - diff --git a/test/prowl_test.rb b/test/prowl_test.rb index 8dd2a12ad..9ffc2122e 100644 --- a/test/prowl_test.rb +++ b/test/prowl_test.rb @@ -8,7 +8,7 @@ def setup def test_push @stubs.post "/publicapi/add" do |env| assert_equal 'api.prowlapp.com', env[:url].host - data = Rack::Utils.parse_query(env[:body]) + data = Faraday::Utils.parse_query(env[:body]) assert_equal 'a', data['apikey'] [200, {}, ''] end diff --git a/test/pushbullet_test.rb b/test/pushbullet_test.rb new file mode 100644 index 000000000..73a17819f --- /dev/null +++ b/test/pushbullet_test.rb @@ -0,0 +1,48 @@ +require File.expand_path('../helper', __FILE__) + +class PushbulletTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @options = { + "api_key" => "e4f235a3851g396d801772b504898140", + "device_iden" => "udhfWdjz4gRNO0Aq" + } + end + + def test_push + stub_request "push" + service(:push, @options, payload).receive_event + end + + def test_push_no_device_iden + @options["device_iden"] = "" + stub_request "push" + service(:push, @options, payload).receive_event + end + + def test_issue + stub_request "issues" + service(:issues, @options, issues_payload).receive_event + end + + def test_pull_request + stub_request "pull_request" + service(:pull_request, @options, pull_payload).receive_event + end + + def stub_request(event) + @stubs.post "/github" do |env| + assert_equal 'https', env[:url].scheme + assert_equal "webhook.pushbullet.com", env[:url].host + body = JSON.parse(env[:body]) + assert_equal event, body['event'] + assert_equal @options, body['config'] + [200, {}, ''] + end + end + + def service(*args) + super(Service::Pushbullet, *args) + end +end diff --git a/test/pushover_test.rb b/test/pushover_test.rb new file mode 100644 index 000000000..60aa01736 --- /dev/null +++ b/test/pushover_test.rb @@ -0,0 +1,30 @@ +require File.expand_path('../helper', __FILE__) + +class PushoverTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + svc = service({"user_key" => "a", "device_name" => "hi"}, payload) + + def svc.shorten_url(*args) + "short" + end + + @stubs.post "/1/messages.json" do |env| + assert_equal "api.pushover.net", env[:url].host + data = Faraday::Utils.parse_query(env[:body]) + assert_equal "a", data["user"] + assert_equal "hi", data["device"] + [200, {}, ''] + end + + svc.receive_push + end + + def service(*args) + super Service::Pushover, *args + end +end + diff --git a/test/railsbp_test.rb b/test/railsbp_test.rb deleted file mode 100644 index f94c48e4d..000000000 --- a/test/railsbp_test.rb +++ /dev/null @@ -1,87 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class RailsbpTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - @svc = service(data, payload) - end - - def test_reads_token_from_data - assert_equal "xAAQZtJhYHGagsed1kYR", @svc.token - end - - def test_reads_default_railsbp_url_from_data - assert_equal "https://railsbp.com", @svc.railsbp_url - end - - def test_reads_custom_railsbp_url_from_data - data = { "token" => "xAAQZtJhYHGagsed1kYR", "railsbp_url" => "http://railsbp.heroku.com" } - svc = service(data, payload) - assert_equal "http://railsbp.heroku.com", svc.railsbp_url - end - - def test_strips_whitespace_from_form_values - data = { "token" => " xAAQZtJhYHGagsed1kYR ", "railsbp_url" => " http://railsbp.heroku.com " } - svc = service(data, payload) - assert_equal "xAAQZtJhYHGagsed1kYR", svc.token - assert_equal "http://railsbp.heroku.com", svc.railsbp_url - end - - def test_posts_payload - @stubs.post "/" do |env| - assert_equal payload, JSON.parse(Rack::Utils.parse_query(env[:body])['payload']) - end - @svc.receive_push - end - - def service(*args) - super Service::Railsbp, *args - end - - def data - { "token" => "xAAQZtJhYHGagsed1kYR", 'railsbp_url' => '' } - end - - def payload - { - "before" => "a6ab010bc21151e238c73d5229c36892d51c2d4f", - "repository" => { - "url" => "https =>//github.com/railsbp/rails-bestpractices.com", - "name" => "rails-bestpractice.com", - "description" => "rails-bestpractices.com", - "watchers" => 64, - "forks" => 14, - "private" => 0, - "owner" => { - "email" => "flyerhzm@gmail.com", - "name" => "Richard Huang" - } - }, - "commits" => [ - { - "id" => "af9718a9bee64b9bbbefc4c9cf54c4cc102333a8", - "url" => "https =>//github.com/railsbp/rails-bestpractices.com/commit/af9718a9bee64b9bbbefc4c9cf54c4cc102333a8", - "author" => { - "email" => "flyerhzm@gmail.com", - "name" => "Richard Huang" - }, - "message" => "fix typo in .travis.yml", - "timestamp" => "2011-12-25T18 =>57 =>17+08 =>00", - "modified" => [".travis.yml"] - }, - { - "id" => "473d12b3ca40a38f12620e31725922a9d88b5386", - "url" => "https =>//github.com/railsbp/rails-bestpractices.com/commit/473d12b3ca40a38f12620e31725922a9d88b5386", - "author" => { - "email" => "flyerhzm@gmail.com", - "name" => "Richard Huang" - }, - "message" => "copy config yaml files for travis", - "timestamp" => "2011-12-25T20 =>36 =>34+08 =>00" - } - ], - "after" => "473d12b3ca40a38f12620e31725922a9d88b5386", - "ref" => "refs/heads/master" - } - end -end diff --git a/test/rally_test.rb b/test/rally_test.rb new file mode 100644 index 000000000..3256f8342 --- /dev/null +++ b/test/rally_test.rb @@ -0,0 +1,194 @@ +require File.expand_path('../helper', __FILE__) + +ART_QUERY_RESULT = + { 'S1234' => {'Results' => []}, + 'DE182' => {'Results' => [{'_ref' => 'https://x.y.z/foo/defect/543221.js', 'Name' => 'Goblins', 'FormattedID' => 'DE182'}]}, + 'DE171' => {'Results' => []}, + 'TA97' => {'Results' => []}, + 'TC3212' => {'Results' => []}, + 'TA1294' => {'Results' => []}, + 'TC1143' => {'Results' => []}, + 'DE175' => {'Results' => [{'_ref' => 'https://x.y.z/foo/defect/524175.js', 'Name' => 'Witches', 'FormattedID' => 'DE175'}]}, + 'DE166' => {'Results' => [{'_ref' => 'https://x.y.z/foo/defect/932166.js', 'Name' => 'Trollop', 'FormattedID' => 'DE166'}]} + } + + +class RallyTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + # For now, test is totally happy-path. + # In future should test for empty data values (server, username, password, workspace, repository) + # raise_config_error on bad form values(server, username, password, workspace) + # graceful failure on inability to create scmrepository, changeset, change based on Rally credentials + # test for no payload + # test for no payload repository, commits, ref + # noop if no commits + # + + def test_push + + @stubs.get '/slm/webservice/1.30/Subscription.js?fetch=Name,Workspaces,Workspace&pretty=true' do |env| + assert_equal 'crubble.rallydev.com', env[:url].host + subs = { 'Name' => "Omicron Bacan Fluffies", + 'Errors' => [], + 'Warnings' => [], + 'Workspaces' => [ {"Name" => "Chloroformer", + "_ref" => "https://crubble.rallydev.com/slm/webservice/1.30/workspace/662372755.js", + "_refObjectName" => "Chlorformer", + } + ] + } + [200, {}, JSON.generate({"Subscription" => subs})] + end + + @stubs.get '/slm/webservice/1.30/scmrepository.js' do |env| + repo = {"Errors" => [], + "Warnings" => [], + "TotalResultCount" => 1, "StartIndex" => 1, "PageSize" => 20, + "Results" => [ + { + "Name" => "Reservoir Dogs", + "_type" => "SCMRepository", + "_ref" => "https://trial.rallydev.com/slm/webservice/1.30/scmrepository/11432875342.js" + } + ] + } + [200, {}, JSON.generate({"QueryResult" => repo})] + end + + @stubs.get '/slm/webservice/1.30/hierarchicalrequirement.js' do |env| + result = artifact_query_response(env[:url]) + [200, {}, result] + end + + @stubs.get '/slm/webservice/1.30/defect.js' do |env| + result = artifact_query_response(env[:url]) + [200, {}, result] + end + + @stubs.get '/slm/webservice/1.30/task.js' do |env| + result = artifact_query_response(env[:url]) + [200, {}, result] + end + + @stubs.get '/slm/webservice/1.30/testcase.js' do |env| + result = artifact_query_response(env[:url]) + [200, {}, result] + end + + @stubs.get '/slm/webservice/1.30/user.js' do |env| + assert_equal 'crubble.rallydev.com', env[:url].host + assert_equal 'https', env[:url].scheme + user_item = { "Name" => "Romeo", + "UserName" => "romeo_must_die", + "_ref" => "https://crubble.rallydev.com/slm/webservice/1.30/user/919235435.js" + } + user_result = {"Errors" => [], "Warnings" => [], "TotalResultCount" => 1, 'Results' => [user_item]} + [200, {}, JSON.generate({'QueryResult' => user_result})] + end + + @stubs.post '/slm/webservice/1.30/scmrepository/create.js' do |env| + repo_result = {'Object' => {"_ref" => 'http://x.y.z/foo/scmrepository/4433556.js'}} + [200, {}, JSON.generate({'CreateResult' => repo_result})] + end + + @stubs.post '/slm/webservice/1.30/Changeset/create.js' do |env| + chgset_result = {"Object" => {"_ref" => 'http://x.y.z/foo/changeset/639214.js'}} + [200, {}, JSON.generate({'CreateResult' => chgset_result})] + end + + @stubs.post '/slm/webservice/1.30/Change/create.js' do |env| + chg_result = {"Object" => {"_ref" => 'http://x.y.z/foo/change/7366456.js'}} + [200, {}, JSON.generate({'CreateResult' => chg_result})] + end + + + data = { 'server' => 'crubble', + 'username' => 'romeo_must_die', + 'password' => 'Plantrachette', + 'workspace' => 'Chloroformer', + 'repository' => 'Reservoir Dogs' + } + payload = rally_test_payload() + + svc = service(data, payload) + svc.receive_push + end + + def artifact_query_response(req) + resp = {"Errors" => [], "Warnings" => [], "TotalResultCount" => 0} + if URI.decode(req.to_s.split('?')[1]) =~ /query=\(FormattedID = ([A-Z]{1,2}\d+)\)/ + art_id = $1 + resp = resp.merge(ART_QUERY_RESULT[art_id]) + resp["TotalResultCount"] = ART_QUERY_RESULT[art_id]["Results"].length + end + return JSON.generate({"QueryResult" => resp}) + end + + def rally_test_payload() + rally_payload = + { "after" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "ref" => "refs/heads/master", + "before" => "4c8124ffcf4039d292442eeccabdeca5af5c5017", + "compare" => "https://github.com/kipster-t/powalla/compare/4c8124ffcf4039d292442eeccabdeca5af5c5017...a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "forced" => false, + "created" => false, + "deleted" => false, + + "repository" => { + "name" => "powalla", + "url" => "https://github.com/kipster-t/powalla", + "owner" => { "name" => "kipster-t", "email" => "klehman@rallydev.com" } + }, + + "pusher" => { + "name" => "YetiShaggy" + }, + + "commits" => [ + { + "id" => "06f63b43050935962f84fe54473a7c5de7977325", + "timestamp" => "2012-01-10T00:11:02-07:00", + "author" => { "name" => "Yeti", "email" => "yeti@rallydev.com" }, + "message" => "Altered S1234 and DE182, Fixed DE171 and Completed TA97. Improved layout and code cohesion", + "added" => ["bus/pricing-model.txt"], + "modified" => ["lib/grit/grit.rb", "test/helper.rb", "test/test_grit.rb"], + "removed" => [], + "url" => "https://github.com/kipster-t/powalla/commit/06f63b43050935962f84fe54473a7c5de7977325", + "distinct" => true + }, + { + "id" => "5057e76a11abd02e83b7d3d3171c4b68d9c88480", + "timestamp" => "2012-01-10T00:18:20-07:00", + "author" => { "name" => "Yeti", "email" => "yeti@rallydev.com" }, + "message" => "clean up heads test, TC3212 cleared, Completed TA1294 and your mama is really mad at your 1954 Mustache.js attitude foda MUS1965 roadasster", + "added" => [], + "modified" => ["test/test_grit.rb"], + "removed" => ["test/test_grotty.rb", "test/test_joobbar.rb"], + "url" => "https://github.com/kipster-t/powalla/commit/5057e76a11abd02e83b7d3d3171c4b68d9c88480", + "distinct" => true + }, + { + "id" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "timestamp" => "2012-01-10T00:50:39-07:00", + "author" => { "name" => "Yeti", "email" => "yeti@rallydev.com" }, + "message" => "TC1143 Passed and DE175 Fixed but DE166 left behind in state of Disrepair", + "added" => ["docs/messieurs.pdf"], + "modified" => ["README", "lib/grit/commiteur.rb"], + "removed" => ["too_gritty.rb"], + "url" => "https://github.com/kipster-t/powalla/commit/a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", + "distinct" => true + } + ] + } + return rally_payload + end + + def service(*args) + super Service::Rally, *args + end + +end + diff --git a/test/rational_team_concert_test.rb b/test/rational_team_concert_test.rb new file mode 100644 index 000000000..2108c76b6 --- /dev/null +++ b/test/rational_team_concert_test.rb @@ -0,0 +1,185 @@ +require File.expand_path('../helper', __FILE__) + +class RationalTeamConcertTest < Service::TestCase + def setup + @stubs= Faraday::Adapter::Test::Stubs.new + @WorkitemsCreated= 0; + @WorkitemsUpdated= 0; + + @stubs.get "/jazz/resource/itemName/com.ibm.team.workitem.WorkItem/51?oslc.properties=oslc:discussedBy" do |env| + assert_common_headers env + assert_common_oslc_headers env + [200, {}, oslc_json_response] + end + @stubs.get "/jazz/resource/itemName/com.ibm.team.workitem.WorkItem/31?oslc.properties=oslc:discussedBy" do |env| + assert_common_headers env + assert_common_oslc_headers env + [200, {}, oslc_json_response] + end + @stubs.get "/jazz/resource/itemName/com.ibm.team.workitem.WorkItem/1?oslc.properties=oslc:discussedBy" do |env| + assert_common_headers env + assert_common_oslc_headers env + [200, {}, oslc_json_response] + end + @stubs.post "/jazz/oslc/workitems/_UIID/rtc_cm:comments/oslc:comment" do |env| + assert_common_headers env + assert_common_oslc_headers env + @WorkitemsUpdated += 1 + [201, {}, ''] + end + @stubs.post "/jazz/oslc/contexts/_UIID/workitems/defect" do |env| + assert_common_headers env + assert_common_oslc_headers env + @WorkitemsCreated += 1 + [201, {}, oslc_json_response] + end + @stubs.post "/jazz/oslc/contexts/_UIID/workitems/enhancement" do |env| + assert_common_headers env + assert_common_oslc_headers env + @WorkitemsCreated += 1 + [201, {}, oslc_json_response] + end + @stubs.post "/jazz/oslc/contexts/_UIID/workitems/story" do |env| + assert_common_headers env + assert_common_oslc_headers env + @WorkitemsCreated += 1 + [201, {}, oslc_json_response] + end + @stubs.get "/jazz/authenticated/identity" do |env| + assert_common_headers env + cookie= env[:request_headers]['Cookie'] == nil ? cookie1 : cookie2; + [201, {"X-com-ibm-team-repository-web-auth-msg" => "authrequired", "set-cookie" => cookie}, ''] + end + @stubs.post "/jazz/authenticated/j_security_check" do |env| + assert_common_headers env + assert_equal 'application/x-www-form-urlencoded', env[:request_headers]['Content-Type'] + assert_equal cookie1, env[:request_headers]['Cookie'] + [201, {}, oslc_json_response] + end + end + + def test_basic_authentication_push_updates + + @formAuthentication= false; + modified_payload= payload.clone() + modified_payload['commits'][0]['message'] << "\n[51] Some message" + modified_payload['commits'][1]['message'] << "\n[31] clean up " + modified_payload['commits'][2]['message'] << "\n[1] Closes tracker item 1" + + svc= service( + {'server_url' => 'https://foo.com/jazz', + 'username' => username, + 'password' => password, + 'project_area_uuid' => '_UIID', + 'basic_authentication' => '1'}, + modified_payload) + svc.receive_push + + assert_equal 0, @WorkitemsCreated + assert_equal 3, @WorkitemsUpdated + end + + def test_basic_authentication_create_new + + @formAuthentication= false; + modified_payload = payload.clone() + modified_payload['commits'][0]['message'] << "\n[defect] Some message" + modified_payload['commits'][1]['message'] << "\n[enhancement] clean up " + modified_payload['commits'][2]['message'] << "\n[story] Closes tracker item 1" + + svc = service( + {'server_url' => 'https://foo.com/jazz', + 'username' => username, + 'password' => password, + 'project_area_uuid' => '_UIID', + 'basic_authentication' => '1'}, + modified_payload) + svc.receive_push + + assert_equal 3, @WorkitemsCreated + assert_equal 3, @WorkitemsUpdated + end + + def test_form_authentication_push_updates + + @formAuthentication= true; + modified_payload = payload.clone() + modified_payload['commits'][0]['message'] << "\n[51] Some message" + modified_payload['commits'][1]['message'] << "\n[31] clean up " + modified_payload['commits'][2]['message'] << "\n[1] Closes tracker item 1" + + svc = service( + {'server_url' => 'https://foo.com/jazz', + 'username' => username, + 'password' => password, + 'project_area_uuid' => '_UIID', + 'basic_authentication' => '0'}, + modified_payload) + svc.receive_push + + assert_equal 0, @WorkitemsCreated + assert_equal 3, @WorkitemsUpdated + end + + def test_form_authentication_create_new + + @formAuthentication= true; + modified_payload = payload.clone() + modified_payload['commits'][0]['message'] << "\n[defect] Some message" + modified_payload['commits'][1]['message'] << "\n[enhancement] clean up " + modified_payload['commits'][2]['message'] << "\n[story] Closes tracker item 1" + + svc = service( + {'server_url' => 'https://foo.com/jazz', + 'username' => username, + 'password' => password, + 'project_area_uuid' => '_UIID', + 'basic_authentication' => '0'}, + modified_payload) + svc.receive_push + + assert_equal 3, @WorkitemsCreated + assert_equal 3, @WorkitemsUpdated + end + + def assert_common_headers env + assert_equal username, env[:request_headers]['X-com-ibm-team-userid'] + assert_equal 'foo.com', env[:url].host + end + + def assert_common_oslc_headers env + assert_equal 'application/json', env[:request_headers]['Content-Type'] + assert_equal 'application/json', env[:request_headers]['accept'] + assert_equal '2.0', env[:request_headers]['oslc-core-version'] + assert_equal cookie2, env[:request_headers]['Cookie'] if @formAuthentication + assert_equal "Basic " + Base64.encode64("#{username}:#{password}").gsub("\n", ""), env[:request_headers]['authorization'] if not @formAuthentication + end + + def oslc_json_response + return '{ + "oslc:discussedBy": { + "rdf:resource": "https://foo.com/jazz/oslc/workitems/_UIID/rtc_cm:comments" + } + }' + end + + def username + return 'test_user' + end + + def password + return 'test_pass' + end + + def cookie1 + return "JSESSIONID=abcd123456" + end + + def cookie2 + return "JSESSIONID=abcd12345678910" + end + def service(*args) + super Service::RationalTeamConcert, *args + end +end + diff --git a/test/rdocinfo_test.rb b/test/rdocinfo_test.rb index adebf455a..7f43c9b89 100644 --- a/test/rdocinfo_test.rb +++ b/test/rdocinfo_test.rb @@ -1,24 +1,27 @@ require File.expand_path('../helper', __FILE__) class RDocInfoTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end + include Service::HttpTestMethods def test_push @stubs.post "/checkout" do |env| - assert_equal 'rubydoc.info', env[:url].host - data = Rack::Utils.parse_query(env[:body]) - assert_equal 1, JSON.parse(data['payload'])['a'] + assert_equal 'www.rubydoc.info', env[:url].host + body = JSON.parse(env[:body]) + assert_equal 'push', body['event'] + assert_equal 'test', body['payload']['repository']['id'] [200, {}, ''] end - svc = service({}, :a => 1) - svc.receive_push + payload = { 'repository' => { 'id' => 'test' }} + svc = service({}, payload) + svc.receive_event + @stubs.verify_stubbed_calls end - def service(*args) - super Service::RDocInfo, *args + private + + def service_class + Service::RDocInfo end end diff --git a/test/read_the_docs_test.rb b/test/read_the_docs_test.rb index 09ba36601..3e2307234 100644 --- a/test/read_the_docs_test.rb +++ b/test/read_the_docs_test.rb @@ -8,7 +8,7 @@ def setup def test_push @stubs.post "/github" do |env| assert_equal 'readthedocs.org', env[:url].host - data = Rack::Utils.parse_query(env[:body]) + data = Faraday::Utils.parse_query(env[:body]) assert_equal 1, JSON.parse(data['payload'])['a'] [200, {}, ''] end diff --git a/test/redmine_test.rb b/test/redmine_test.rb index 66b2a3073..aed046853 100644 --- a/test/redmine_test.rb +++ b/test/redmine_test.rb @@ -8,17 +8,73 @@ def setup def test_push @stubs.get "/a/sys/fetch_changesets" do |env| assert_equal 'r.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) + data = Faraday::Utils.parse_nested_query(env[:body]) assert_equal 'a', env[:params]['key'] assert_equal 'p', env[:params]['id'] [200, {}, ''] end svc = service({'address' => 'http://r.com/a/', - 'api_key' => 'a', 'project' => 'p'}, :a => 1) + 'api_key' => 'a', 'project' => 'p', 'fetch_github_commits' => true }, :a => 1) svc.receive_push end + def test_update_issue_module_to_root_redmine_path + @stubs.put "/issues/1234.json" do |env| + assert_equal 'redmine.org', env[:url].host + assert_equal 'application/json', env[:request_headers]['Content-type'] + assert_equal 'API_KEY-654321', env[:request_headers]['X-Redmine-API-Key'] + assert env[:params]['issue']['notes'].include?("Author: Mahmoud") + [200, {}, ''] + end + configurations = { + 'address' => "http://redmine.org", + 'api_key' => "API_KEY-654321", + 'update_redmine_issues_about_commits' => '1' + } + payloads = { + 'commits' => [ + { 'message' => "FIX Issue #1234", + 'timestamp' => "2007-10-10T00:11:02-07:00", + 'id' => "b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33", + 'url' => "https://github.com/modsaid/github-services/commit/b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33", + 'author' => {'name' => "Mahmoud", 'email' => "modsaid@example.com"}, + 'added' => [], 'removed' => [], 'modified' => [] + } + ] + } + svc = service(configurations, payloads) + assert svc.receive_push + end + + def test_update_issue_module_to_non_root_redmine_path + @stubs.put "/a/issues/1234.json" do |env| + assert_equal 'redmine.org', env[:url].host + assert_equal 'application/json', env[:request_headers]['Content-type'] + assert_equal 'API_KEY-654321', env[:request_headers]['X-Redmine-API-Key'] + assert env[:params]['issue']['notes'].include?("Author: Mahmoud") + [200, {}, ''] + end + configurations = { + 'address' => "http://redmine.org/a", + 'api_key' => "API_KEY-654321", + 'update_redmine_issues_about_commits' => '1' + } + payloads = { + 'commits' => [ + { 'message' => "FIX Issue #1234", + 'timestamp' => "2007-10-10T00:11:02-07:00", + 'id' => "b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33", + 'url' => "https://github.com/modsaid/github-services/commit/b44aa57a6c6c52cc20b9e396cfe3cf97bdfc2b33", + 'author' => {'name' => "Mahmoud", 'email' => "modsaid@example.com"}, + 'added' => [], 'removed' => [], 'modified' => [] + } + ] + } + svc = service(configurations, payloads) + assert svc.receive_push + end + def service(*args) super Service::Redmine, *args end diff --git a/test/rubyforge_test.rb b/test/rubyforge_test.rb deleted file mode 100644 index 7a2ec826b..000000000 --- a/test/rubyforge_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class RubyforgeTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({'groupid' => 'g'}, payload) - def svc.news - @news ||= [] - end - - def svc.post_news(*args) - news << args - end - - svc.receive_push - assert news = svc.news.shift - assert_equal 'g', news[0] - assert_match '06f63b43050935962f84fe54473a7c5de7977325', news[1] - assert_match 'stub git call', news[2] - end - - def service(*args) - super Service::Rubyforge, *args - end -end - diff --git a/test/schema_test.rb b/test/schema_test.rb new file mode 100644 index 000000000..f2e4bb974 --- /dev/null +++ b/test/schema_test.rb @@ -0,0 +1,171 @@ +require File.expand_path('../helper', __FILE__) + +class DefaultSchemaTest < Service::TestCase + class SchemaService < Service + end + + def setup + @svc = SchemaService + end + + def test_title + assert_equal 'SchemaService', @svc.title + end + + def test_hook_name + assert_equal 'schemaservice', @svc.hook_name + end + + def test_default_events + assert_equal [:push], @svc.default_events + end + + def test_supported_events + assert_equal [], @svc.supported_events + end + + def test_schema + assert_equal [], @svc.schema + end + + def test_white_listed_attributes + assert_equal [], @svc.white_listed + end + + def test_url + assert_nil @svc.url + end + + def test_logo_url + assert_nil @svc.logo_url + end + + def test_maintainers + assert_equal [], @svc.maintainers + end + + def test_supporters + assert_equal [], @svc.supporters + end +end + +class DefaultSchemaWithEventsTest < DefaultSchemaTest + class SchemaService < Service + def receive_push + end + + def receive_issues + end + end + + def setup + @svc = SchemaService + end + + def test_supported_events + assert_equal %w(issues push), @svc.supported_events.sort + end +end + +class DefaultSchemaWithAllEventsTest < DefaultSchemaTest + class SchemaService < Service + def receive_event + end + + def receive_push + end + + def receive_issues + end + end + + def setup + @svc = SchemaService + end + + def test_supported_events + assert_equal Service::ALL_EVENTS, @svc.supported_events.sort + end +end + +class CustomSchemaTest < DefaultSchemaTest + class SchemaService < Service + title "Custom!" + hook_name "custom" + + string :abc + password :def + boolean :ghi + + white_list :abc, :ghi + + url 'url' + logo_url 'logo' + + maintained_by :email => 'abc@def.com', + :web => 'http://def.com/support', + :github => 'abc', + :twitter => 'def' + + supported_by :email => 'abc@def.com', + :web => 'http://def.com/support', + :github => %w(abc def), + :twitter => 'def' + end + + def setup + @svc = SchemaService + end + + def test_title + assert_equal 'Custom!', @svc.title + end + + def test_hook_name + assert_equal 'custom', @svc.hook_name + end + + def test_schema + assert_equal [ + [:string, :abc], + [:password, :def], + [:boolean, :ghi]], @svc.schema + end + + def test_white_listed_attributes + assert_equal %w(abc ghi), @svc.white_listed + end + + def test_maintainers + maintainers = @svc.maintainers + assert_contributor :email, 'abc@def.com', maintainers + assert_contributor :web, 'http://def.com/support', maintainers + assert_contributor :github, 'abc', maintainers + assert_contributor :twitter, 'def', maintainers + assert_equal 4, maintainers.size + end + + def test_supporters + supporters = @svc.supporters + assert_contributor :email, 'abc@def.com', supporters + assert_contributor :web, 'http://def.com/support', supporters + assert_contributor :github, 'abc', supporters + assert_contributor :github, 'def', supporters + assert_contributor :twitter, 'def', supporters + assert_equal 5, supporters.size + end + + def test_url + assert_equal 'url', @svc.url + end + + def test_logo_url + assert_equal 'logo', @svc.logo_url + end + + def assert_contributor(contributor_type, value, contributors) + assert contributors.detect { |c| c.class.contributor_type == contributor_type && + c.value == value } + end +end + diff --git a/test/scrumdo_test.rb b/test/scrumdo_test.rb index 2ce82574f..95967c734 100644 --- a/test/scrumdo_test.rb +++ b/test/scrumdo_test.rb @@ -8,7 +8,7 @@ def setup def test_push @stubs.post "/hooks/github/slug" do |env| assert_equal 'www.scrumdo.com', env[:url].host - data = Rack::Utils.parse_query(env[:body]) + data = Faraday::Utils.parse_query(env[:body]) assert_equal 'rick', data['username'] assert_equal 'monkey', data['password'] assert data['payload'] diff --git a/test/service_test.rb b/test/service_test.rb index 4731fd5a9..160ed1450 100644 --- a/test/service_test.rb +++ b/test/service_test.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require File.expand_path('../helper', __FILE__) class ServiceTest < Service::TestCase @@ -6,6 +8,11 @@ def receive_push end end + class TestCatchAllService < Service + def receive_event + end + end + def setup @stubs = Faraday::Adapter::Test::Stubs.new @service = service(:push, 'data', 'payload') @@ -15,11 +22,41 @@ def test_receive_valid_event assert TestService.receive :push, {}, {} end + def test_specific_event_method + assert_equal 'receive_push', TestService.new(:push, {}, {}).event_method + end + + def test_catch_all_event_method + assert_equal 'receive_event', TestCatchAllService.new(:push, {}, {}).event_method + end + + def test_missing_method + assert_equal nil, TestService.new(:issues, {}, {}).event_method + end + + def test_http_callback + @stubs.post '/' do |env| + [200, {'x-test' => 'booya'}, 'ok'] + end + + @service.http.post '/' + + @service.http_calls.each do |env| + assert_equal '/', env[:request][:url] + assert_equal '0', env[:request][:headers]['Content-Length'] + assert_equal 200, env[:response][:status] + assert_equal 'booya', env[:response][:headers]['x-test'] + assert_equal 'ok', env[:response][:body] + end + + assert_equal 1, @service.http_calls.size + end + def test_url_shorten url = "http://github.com" @stubs.post "/" do |env| assert_equal 'git.io', env[:url].host - data = Rack::Utils.parse_query(env[:body]) + data = Faraday::Utils.parse_query(env[:body]) assert_equal url, data['url'] [201, {'Location' => 'short'}, ''] end @@ -42,6 +79,164 @@ def http.post end end + def test_http_only_with_prefix + ["ftp://1.1.1.1", "file:///etc/passwd"].each do |url| + http = @service.http + http.url_prefix = URI::parse(url) + + assert_raises Service::ConfigurationError do + @service.http_post "/this/is/a/url" + end + assert_raises Service::ConfigurationError do + @service.http_get "/this/is/a/url" + end + end + end + + def test_http_only_with_full_url + ["ftp://1.1.1.1", "file:///etc/passwd"].each do |url| + http = @service.http + + assert_raises Service::ConfigurationError do + @service.http_post url + end + assert_raises Service::ConfigurationError do + @service.http_get url + end + end + end + + def test_http_only_with_prefix_and_fqdn + ["ftp://1.1.1.1", "file:///etc/passwd"].each do |url| + http = @service.http + http.url_prefix = URI::parse(url) + + assert_raises Service::ConfigurationError do + @service.http_post "ftp:///this/is/a/url" + end + assert_raises Service::ConfigurationError do + @service.http_get "ftp:///this/is/a/url" + end + end + end + + def test_http_get_url_strip + stubs = Faraday::Adapter::Test::Stubs.new + stubs.get("/") { |env| [200, {}, "ok"] } + stubs.get("/ ") { |env| [200, {}, "nope"] } + + service = TestService.new(:push, "data", "payload") + service.http :adapter => [:test, stubs] + + service.http_get "https://example.com/ " + http_call = service.http_calls[0] + assert_equal "https://example.com/", http_call[:request][:url] + assert_equal "ok", http_call[:response][:body] + end + + def test_http_post_url_strip + stubs = Faraday::Adapter::Test::Stubs.new + stubs.post("/") { |env| [200, {}, "ok"] } + stubs.post("/ ") { |env| [200, {}, "nope"] } + + service = TestService.new(:push, "data", "payload") + service.http :adapter => [:test, stubs] + + service.http_post "https://example.com/ " + http_call = service.http_calls[0] + assert_equal "https://example.com/", http_call[:request][:url] + assert_equal "ok", http_call[:response][:body] + end + + def test_json_encoding + payload = {'unicodez' => "rtiaΓΌ\n\n€ý5:q"} + json = @service.generate_json(payload) + assert_equal payload, JSON.parse(json) + end + + def test_config_boolean_true_helper + svc = service(:push, "is_checked" => nil) + refute svc.config_boolean_true?("is_checked") + + svc = service(:push, "is_checked" => 0) + refute svc.config_boolean_true?("is_checked") + + svc = service(:push, "is_checked" => "0") + refute svc.config_boolean_true?("is_checked") + + svc = service(:push, "is_checked" => 1) + assert svc.config_boolean_true?("is_checked") + + svc = service(:push, "is_checked" => "1") + assert svc.config_boolean_true?("is_checked") + end + + def test_before_delivery + @service.before_delivery do |url, payload, headers, params| + headers['EDITED-IN-BEFORE-DELIVERY'] = true + payload.replace("EDITED") + end + + @stubs.post '/' do |env| + assert_equal '/', env.url.to_s + assert_equal 'EDITED', env[:body] + assert_equal true, env[:request_headers]['Edited-In-Before-Delivery'] + [200, {'X-Test' => 'success'}, 'OK'] + end + + @service.http_post('/', payload.to_s) + + @service.http_calls.each do |env| + assert_equal 200, env[:response][:status] + end + + assert_equal 1, @service.http_calls.size + end + + def test_multiple_before_delivery_callbacks + @service.before_delivery do |url, payload, headers, params| + headers['EDITED-IN-BEFORE-DELIVERY-1'] = true + end + + @service.before_delivery do |url, payload, headers, params| + headers['EDITED-IN-BEFORE-DELIVERY-2'] = true + end + + @stubs.get '/' do |env| + assert_equal true, env[:request_headers]['Edited-In-Before-Delivery-1'] + assert_equal true, env[:request_headers]['Edited-In-Before-Delivery-2'] + [200, {'X-Test' => 'success'}, 'OK'] + end + + @service.http_get('/') + + @service.http_calls.each do |env| + assert_equal 200, env[:response][:status] + end + end + + def test_reset_pre_delivery_callbacks! + @service.before_delivery do |url, payload, headers, params| + headers['EDITED-IN-BEFORE-DELIVERY'] = true + payload.replace("EDITED") + end + + @stubs.post '/' do |env| + assert_equal 'EDITED', env[:body] + assert_equal true, env[:request_headers]['Edited-In-Before-Delivery'] + [200, {'X-Test' => 'success'}, 'OK'] + end + + @service.http_post('/', "desrever") + @service.reset_pre_delivery_callbacks! + + @stubs.post '/' do |env| + refute_equal 'EDITED', env[:body] + refute_equal true, env[:request_headers]['Edited-In-Before-Delivery'] + [200, {'X-Test' => 'success'}, 'OK'] + end + end + def service(*args) super TestService, *args end diff --git a/test/sifter_test.rb b/test/sifter_test.rb new file mode 100644 index 000000000..19943ad64 --- /dev/null +++ b/test/sifter_test.rb @@ -0,0 +1,51 @@ +require File.expand_path('../helper', __FILE__) + +class SifterTest < Service::TestCase + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @svc = service(data, payload) + end + + def test_reads_token + assert_equal token, @svc.token + end + + def test_reads_subdomain + assert_equal 'example', @svc.subdomain + end + + def test_implies_host + assert_equal "https://example.sifterapp.com/api/github", @svc.hook_url + + ENV['SIFTER_HOST'] = 'sifter.dev' + assert_equal "http://example.sifter.dev/api/github", @svc.hook_url + ENV.delete('SIFTER_HOST') + end + + def test_posts_payload + @stubs.post '/api/github' do |env| + assert_equal 'https', env[:url].scheme + assert_equal 'example.sifterapp.com', env[:url].host + assert_equal token, env[:params]['token'] + assert_equal payload, JSON.parse(env[:body]) + end + + @svc.receive_push + end + + private + + def service(*args) + super Service::Sifter, *args + end + + def data + {'token' => token + ' ' * 4, 'subdomain' => 'example'} + end + + def token + 'NTpuZXh0dXckYX4lOjE%3D' + end + +end diff --git a/test/simperium_test.rb b/test/simperium_test.rb new file mode 100644 index 000000000..748c9eace --- /dev/null +++ b/test/simperium_test.rb @@ -0,0 +1,40 @@ +require File.expand_path('../helper', __FILE__) + +class SimperiumTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + test_app_id = "sample-app-name" + test_token = "0123456789abcde" + test_bucket = "github-event" + + data = { + 'app_id' => test_app_id, + 'token' => test_token, + 'bucket' => test_bucket + } + + payload = {'commits'=>[{'id'=>'test'}]} + svc = service(data, payload) + + @stubs.post "/1/#{test_app_id}/#{test_bucket}/i/#{svc.delivery_guid}" do |env| + body = JSON.parse(env[:body]) + + assert_equal env[:url].host, "api.simperium.com" + assert_equal env[:request_headers]['Authorization'], "Token #{test_token}" + assert_equal 'test', body['payload']['commits'][0]['id'] + assert_match 'guid-', body['guid'] + assert_equal data, body['config'] + assert_equal 'push', body['event'] + [200, {}, ''] + end + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service_class + Service::Simperium + end +end + diff --git a/test/skydeskprojects_test.rb b/test/skydeskprojects_test.rb new file mode 100644 index 000000000..e1c171b52 --- /dev/null +++ b/test/skydeskprojects_test.rb @@ -0,0 +1,30 @@ +require File.expand_path('../helper', __FILE__) + +class SkyDeskProjectsTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + url = "/serviceHook" + data = { + "project_id" => "1234", + "token" => "a13d", + } + svc = service(data, payload) + @stubs.post url do |env| + assert_equal 'projects.skydesk.jp', env[:url].host + params = Faraday::Utils.parse_query(env[:body]) + assert_equal '1234', params['pId'] + assert_equal 'a13d', params['authtoken'] + assert_equal payload, JSON.parse(params['payload']) + [200, {}, ''] + + end + svc.receive + end + + def service(*args) + super Service::SkyDeskProjects, *args + end +end diff --git a/test/smartling_test.rb b/test/smartling_test.rb new file mode 100644 index 000000000..898ac8574 --- /dev/null +++ b/test/smartling_test.rb @@ -0,0 +1,159 @@ +require File.expand_path('../helper', __FILE__) + +class SmartlingTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_requires_service_url + data = self.data.update("service_url" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_requires_project_id + data = self.data.update("project_id" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_requires_api_key + data = self.data.update("api_key" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_requires_config_path + data = self.data.update("config_path" => "") + svc = service :push, data, payload + + assert_raises Service::ConfigurationError do + svc.receive + end + end + + def test_requires_master_only_no_master + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def test_requires_master_only_no_branch + payload = self.payload.update("ref" => "refs/heads/branch_name") + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def test_requires_master_only_nil_master + data = self.data.update("master_only" => nil) + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def test_requires_master_only_nil_branch + data = self.data.update("master_only" => nil) + payload = self.payload.update("ref" => "refs/heads/branch_name") + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def test_requires_master_only_yes_master + data = self.data.update("master_only" => "1") + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def test_requires_master_only_yes_branch + payload = self.payload.update("ref" => "refs/heads/branch_name") + data = self.data.update("master_only" => "1") + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + begin + @stubs.verify_stubbed_calls + rescue RuntimeError + else + assert_true false + end + end + + def test_error + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + [401, {}, ''] + end + svc = service :push, data, payload + begin + svc.receive + rescue Service::ConfigurationError + else + assert_true false + end + @stubs.verify_stubbed_calls + end + + def test_ok + @stubs.post "/github" do |env| + assert_equal "capi.smatling.com", env[:url].host + body = JSON.parse(env[:body]) + assert_equal data["project_id"], body.delete("projectId") + assert_equal data["api_key"], body.delete("apiKey") + assert_equal data["config_path"], body.delete("resourceFile") + assert_equal payload, body + [200, {}, ''] + end + svc = service :push, data, payload + svc.receive + @stubs.verify_stubbed_calls + end + + def data + { + "service_url" => "http://capi.smatling.com", + "project_id" => "d86077368", + "api_key" => "2c1ad0bb-e9b6-4c20-b536-1006502644a2", + "config_path" => "smartling-config.json", + "master_only" => "0" + } + end + + def service(*args) + super Service::Smartling, *args + end +end diff --git a/test/socialcast_test.rb b/test/socialcast_test.rb deleted file mode 100644 index 563b67cba..000000000 --- a/test/socialcast_test.rb +++ /dev/null @@ -1,29 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class SocialcastTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/api/messages.xml" do |env| - assert_equal 'd', env[:url].host - assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] - data = Rack::Utils.parse_nested_query(env[:body]) - msg = data['message'] - assert_match 'Tom Preston-Werner', msg['body'] - assert_match '3 commits', msg['title'] - assert_equal 'g', msg['group_id'] - [200, {}, ''] - end - - svc = service({'username' => 'u', 'password' => 'p', - 'group_id' => 'g', 'api_domain' => 'd'}, payload) - svc.receive_push - end - - def service(*args) - super Service::Socialcast, *args - end -end - diff --git a/test/softlayer_messaging_test.rb b/test/softlayer_messaging_test.rb new file mode 100644 index 000000000..ee6ea880c --- /dev/null +++ b/test/softlayer_messaging_test.rb @@ -0,0 +1,114 @@ +require File.expand_path('../helper', __FILE__) + +class SoftLayerMessagingTest < Service::TestCase + include Service::PushHelpers + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push_topic + svc = service({"user"=>"test_user", "key"=>"test_apikey", + "name"=>"test_topic", "account" => "test_account", "topic"=>true}, payload) + svc.client = fake_client + + svc.receive_push + + payload_json_data = JSON.generate(payload) + auth = [ "test_user", "test_apikey" ] + options = { + :fields => { + :repository => payload['repository']['name'], + :owner => payload['repository']['owner']['name'], + :email => payload['repository']['owner']['email'], + :ref => payload['ref'], + :created => payload['created'], + :forced => payload['forced'], + :deleted => payload['deleted'] + } + } + assert_equal auth, fake_client.called['client.authenticate'] + assert_equal ['test_topic'], fake_client.called['client.topic'] + assert_equal ['test_topic', payload_json_data], fake_client.called['queue.publish'][0,2] + assert_equal options, fake_client.called['queue.publish'][2] + end + + def test_push_queue + svc = service({"user"=>"test_user", "key"=>"test_apikey", + "name"=>"test_queue", "account" => "test_account", "topic"=>false}, payload) + svc.client = fake_client + + svc.receive_push + + payload_json_data = JSON.generate(payload) + auth = [ "test_user", "test_apikey" ] + options = { + :fields => { + :repository => payload['repository']['name'], + :owner => payload['repository']['owner']['name'], + :email => payload['repository']['owner']['email'], + :ref => payload['ref'], + :created => payload['created'], + :forced => payload['forced'], + :deleted => payload['deleted'] + } + } + + assert_equal auth, fake_client.called['client.authenticate'] + assert_equal ['test_queue'], fake_client.called['client.queue'] + assert_equal ['test_queue', payload_json_data], fake_client.called['queue.push'][0,2] + assert_equal options, fake_client.called['queue.push'][2] + end + + def service(*args) + super Service::SoftLayerMessaging, *args + end + + def fake_client + @fake_client ||= FakeSoftLayerClient.new + end + + class FakeSoftLayerClient + attr_reader :called + + def initialize + @called = {} + end + + def record_call(name, args) + @called[name] = args + end + + def queue(name) + record_call('client.queue', [name]) + FakeTopicQueue.new(name, self) + end + + def topic(name) + record_call('client.topic', [name]) + FakeTopicQueue.new(name, self) + end + + def authenticate(user, key) + record_call('client.authenticate', [user, key]) + end + + end + + class FakeTopicQueue + + def initialize(name, client) + @name = name + @client = client + end + + def push(payload, options) + @client.record_call('queue.push', [@name, payload, options]) + end + + def publish(payload, options) + @client.record_call('queue.publish', [@name, payload, options]) + end + end + +end diff --git a/test/sourcemint_test.rb b/test/sourcemint_test.rb deleted file mode 100644 index a5932fbac..000000000 --- a/test/sourcemint_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class SourcemintTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - url = "/actions/post-commit" - @stubs.post url do |env| - assert_equal 'api.sourcemint.com', env[:url].host - assert_equal "payload=%22payload%22", env[:body] - [200, {}, ''] - end - - svc = service :push, {}, 'payload' - svc.receive - end - - def service(*args) - super Service::Sourcemint, *args - end -end - diff --git a/test/splendid_bacon_test.rb b/test/splendid_bacon_test.rb deleted file mode 100644 index d586e4ba5..000000000 --- a/test/splendid_bacon_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class SplendidBaconTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post "/api/v1/projects/p/github" do |env| - assert_equal 'splendidbacon.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) - assert_equal 1, JSON.parse(data['payload'])['a'] - [200, {}, ''] - end - - svc = service({'token' => 't', 'project_id' => 'p'}, :a => 1) - svc.receive_push - end - - def service(*args) - super Service::SplendidBacon, *args - end -end - diff --git a/test/sprintly_test.rb b/test/sprintly_test.rb new file mode 100644 index 000000000..c46a54a0b --- /dev/null +++ b/test/sprintly_test.rb @@ -0,0 +1,31 @@ +require File.expand_path('../helper', __FILE__) + +class SprintlyTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/integration/github/1/push/" do |env| + assert_equal 'sprint.ly', env[:url].host + assert_equal 'application/json', + env[:request_headers]['content-type'] + assert_equal basic_auth("my_user@foo.bar", "my_api_key"), + env[:request_headers]['authorization'] + [200, {}, ''] + end + + svc = service :push, { + 'email' => 'my_user@foo.bar', + 'product_id' => '1', + 'api_key' => 'my_api_key' + }, payload + + svc.receive_event + @stubs.verify_stubbed_calls + end + + def service(*args) + super Service::Sprintly, *args + end +end diff --git a/test/sqs_queue_test.rb b/test/sqs_queue_test.rb new file mode 100644 index 000000000..f3ae2f183 --- /dev/null +++ b/test/sqs_queue_test.rb @@ -0,0 +1,76 @@ +require File.expand_path('../helper', __FILE__) +ENV['SQS_STUB_REQUESTS'] = 'true' + +class SqsQueueTest < Service::TestCase + include Service::PushHelpers + + attr_reader :payload, :data + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @old_data = { + 'aws_access_key' => ' AIQPJBLDKSU8SKLZNHGLQA', + 'aws_secret_key' => 'jaz8OQ72kzmblq9TYY28alqp9y7Zmvlsq9iJJqAA ', + 'sqs_queue_name' => ' testQueue ' + } + @data = { + 'aws_sqs_arn' => "arn:aws:sqs:us-west-2:1234567890:testqueue", + 'aws_access_key' => ' AIQPJBLDKSU8SKLZNHGLQA', + 'aws_secret_key' => 'jaz8OQ72kzmblq9TYY28alqp9y7Zmvlsq9iJJqAA ' + } + end + + def test_strip_whitespace_from_form_data + svc = service(@old_data, payload) + assert_equal 'AIQPJBLDKSU8SKLZNHGLQA', svc.access_key + assert_equal 'jaz8OQ72kzmblq9TYY28alqp9y7Zmvlsq9iJJqAA', svc.secret_key + assert_equal 'testQueue', svc.queue_name + end + + def test_aws_key_lengths + svc = service(@old_data, payload) + assert_equal 22, svc.access_key.length + assert_equal 40, svc.secret_key.length + end + + def service(*args) + super Service::SqsQueue, *args + end + + def test_sets_queue_name_with_arn + svc = service(@data, payload) + assert_equal 'testqueue', svc.queue_name + end + + def test_sets_region_with_old_data + svc = service(@old_data, payload) + assert_equal 'us-east-1', svc.region + end + + def test_sets_region_with_new_data + svc = service(@data, payload) + assert_equal 'us-west-2', svc.region + end + + def test_notify_sqs_sends_message_attributes + svc = service(@data, payload) + client = svc.sqs_client + client.client.new_stub_for(:send_message) + queue_url_resp = client.client.stub_for(:get_queue_url) + queue_url_resp.data[:queue_url] = 'https://sqs.us-west-2.amazonaws.com/1234567890/testQueue' + + result = svc.notify_sqs(svc.access_key, svc.secret_key, '{type: ping}') + + # make sure the original params are what is expected + original_params = result.request_options + assert_equal 'https://sqs.us-west-2.amazonaws.com/1234567890/testQueue', original_params[:queue_url] + assert_equal '{type: ping}', original_params[:message_body] + expected_hash = { + 'X-GitHub-Event' => {:string_value => 'push', :data_type => 'String'} + } + assert_equal expected_hash, original_params[:message_attributes] + + end + +end diff --git a/test/stackmob_test.rb b/test/stackmob_test.rb deleted file mode 100644 index 7236d7f10..000000000 --- a/test/stackmob_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class StackmobTest < Service::TestCase - - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push_valid - token = "abcdefg" - @stubs.post "/callback/#{token}" do |env| - assert_equal payload.to_json, env[:body] - [200, {}, ''] - end - - svc = service :push, { Service::Stackmob::TOKEN_KEY => token }, payload - svc.receive_push - @stubs.verify_stubbed_calls - end - - def test_push_missing_token - svc = service :push, { Service::Stackmob::TOKEN_KEY => '' }, payload - assert_raises Service::ConfigurationError do - svc.receive_push - end - end - - def service(*args) - super Service::Stackmob, *args - end - -end diff --git a/test/statusnet_test.rb b/test/statusnet_test.rb index ae20b60e8..f74a2a375 100644 --- a/test/statusnet_test.rb +++ b/test/statusnet_test.rb @@ -8,7 +8,7 @@ def setup def test_push @stubs.post "/api/statuses/update.xml" do |env| assert_equal 's.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) + data = Faraday::Utils.parse_nested_query(env[:body]) assert_equal 'github', data['source'] [200, {}, ''] end diff --git a/test/talker_test.rb b/test/talker_test.rb index 729828dc6..89ebd6f97 100644 --- a/test/talker_test.rb +++ b/test/talker_test.rb @@ -54,7 +54,7 @@ def stub_message_posting @stubs.post "/room/1/messages.json" do |env| assert_equal 's.talkerapp.com', env[:url].host assert_equal 't', env[:request_headers]['x-talker-token'] - data = Rack::Utils.parse_nested_query(env[:body]) + data = Faraday::Utils.parse_nested_query(env[:body]) assert data.key?('message') [200, {}, ''] end diff --git a/test/target_process_test.rb b/test/target_process_test.rb index e5c1f5a9b..15c7a56fd 100644 --- a/test/target_process_test.rb +++ b/test/target_process_test.rb @@ -16,14 +16,14 @@ def test_push @stubs.get "/tp/api/v1/Users?where=(Email%20eq%20'jonnyfunfun@gmail.com')" do |env| assert_equal basic_auth('uz0r', 'p455w0rd'), env[:request_headers]['authorization'] assert_equal "(Email eq 'jonnyfunfun@gmail.com')", env[:params]['where'] - [200, {}, 'jonnyfunfun@gmail.comfoobar@snafu.com'] + [200, {}, 'jonnyfunfun@gmail.comfoobar@snafu.com'] end @stubs.get "/tp/api/v1/Processes/OMGWTFBBQ/EntityStates?where(Name%20eq%20'fubar')%20and%20(EntityType.Name%20eq%20'Bug')" do |env| assert_equal 'ZOMG', env[:params]['acid'] assert_equal "(Name eq 'fubar') and (EntityType.Name eq 'Bug')", env[:params]['where'] assert_equal basic_auth('uz0r', 'p455w0rd'), env[:request_headers]['authorization'] - [200, {}, ''] + [200, {}, ''] end @stubs.get "/tp/api/v1/Assignables/1783?acid=ZOMG&include=%5BEntityType%5D" do |env| diff --git a/test/teamcity_test.rb b/test/teamcity_test.rb index cf5d78305..378a3e121 100644 --- a/test/teamcity_test.rb +++ b/test/teamcity_test.rb @@ -6,10 +6,18 @@ def setup end def test_push - url = "/abc/httpAuth/action.html" - @stubs.get url do |env| + url = "/abc/httpAuth/app/rest/buildQueue" + @stubs.post url do |env| assert_equal 'teamcity.com', env[:url].host - assert_equal 'btid', env[:params]['add2Queue'] + assert_equal '', env[:body] + assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] + [200, {}, ''] + end + + url2 = "abc/httpAuth/app/rest/vcs-root-instances/checkingForChangesQueue?locator=buildType:btid" + @stubs.post url2 do |env| + assert_equal 'teamcity.com', env[:url].host + assert_equal nil, env[:body] assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] [200, {}, ''] end @@ -18,11 +26,95 @@ def test_push 'base_url' => 'http://teamcity.com/abc', 'build_type_id' => 'btid', 'username' => 'u', - 'password' => 'p' + 'password' => 'p', + 'check_for_changes_only' => '0' }, 'payload') svc.receive_push end + def test_push_deleted_branch + url = "/abc/httpAuth/action.html" + @stubs.post url do |env| + assert false, "service should not be called for deleted branches" + end + + svc = service({ + 'base_url' => 'http://teamcity.com/abc', + 'build_type_id' => 'btid' + }, { + 'deleted' => true, + 'ref' => 'refs/heads/branch-name' + }) + svc.receive_push + end + + def test_push_with_branch_name + url = "/abc/httpAuth/app/rest/buildQueue" + @stubs.post url do |env| + assert_equal '', env[:body] + [200, {}, ''] + end + + svc = service({ + 'base_url' => 'http://teamcity.com/abc', + 'build_type_id' => 'btid' + }, { + 'ref' => 'refs/heads/branch-name' + }) + svc.receive_push + end + + def test_push_with_branch_name_incl_slashes + url = "/abc/httpAuth/app/rest/buildQueue" + @stubs.post url do |env| + assert_equal '', env[:body] + [200, {}, ''] + end + + svc = service({ + 'base_url' => 'http://teamcity.com/abc', + 'build_type_id' => 'btid' + }, { + 'ref' => 'refs/heads/branch/name' + }) + svc.receive_push + end + + def test_push_with_branch_full_ref + url = "/abc/httpAuth/app/rest/buildQueue" + @stubs.post url do |env| + assert_equal '', env[:body] + [200, {}, ''] + end + + svc = service({ + 'base_url' => 'http://teamcity.com/abc', + 'build_type_id' => 'btid', + 'full_branch_ref' => '1' + }, { + 'ref' => 'refs/heads/branch/name' + }) + svc.receive_push + end + + def test_push_when_check_for_changes_is_true + url = "abc/httpAuth/app/rest/vcs-root-instances/checkingForChangesQueue?locator=buildType:btid" + @stubs.post url do |env| + assert_equal 'teamcity.com', env[:url].host + assert_equal "", env[:body] + [200, {}, ''] + end + + svc = service({ + 'base_url' => 'http://teamcity.com/abc', + 'build_type_id' => 'btid', + 'check_for_changes_only' => '1' + }, 'payload') + svc.receive_push + end + + + def service(*args) super Service::TeamCity, *args end diff --git a/test/tender_test.rb b/test/tender_test.rb new file mode 100644 index 000000000..b77a4703c --- /dev/null +++ b/test/tender_test.rb @@ -0,0 +1,60 @@ +require File.expand_path('../helper', __FILE__) + +class TenderTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @options = { + 'domain' => 'some.tenderapp.com', + 'token' => 'Aewi5ui1' + } + end + + def test_issues + @stubs.post "/tickets/github/Aewi5ui1" do |env| + body = JSON.parse(env[:body]) + + assert_equal 'https', env[:url].scheme + assert !env[:ssl][:verify] + assert_equal 'some.tenderapp.com', env[:url].host + assert_equal 'application/json', env[:request_headers]['Content-Type'] + + assert_equal body["issue"]["state"], "open" + assert_equal body["issue"]["number"], 5 + assert_equal body["repository"]["name"], "grit" + assert_equal body["repository"]["owner"]["login"], "mojombo" + + [200, {}, ''] + end + + service(:issues, @options, issues_payload).receive_issues + end + + def test_pull + @stubs.post "/tickets/github/Aewi5ui1" do |env| + puts env[:body] + body = JSON.parse(env[:body]) + + assert_equal 'https', env[:url].scheme + assert !env[:ssl][:verify] + assert_equal 'some.tenderapp.com', env[:url].host + assert_equal 'application/json', env[:request_headers]['Content-Type'] + + assert_equal body["pull_request"]["state"], "open" + assert_equal body["pull_request"]["number"], 5 + assert_equal body["repository"]["name"], "grit" + assert_equal body["repository"]["owner"]["login"], "mojombo" + + [200, {}, ''] + end + + service(:pull_request, @options, pull_payload).receive_pull_request + end + + private + + def service(*args) + super Service::Tender, *args + end + +end diff --git a/test/test_pilot_test.rb b/test/test_pilot_test.rb deleted file mode 100644 index 00b4f4153..000000000 --- a/test/test_pilot_test.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class TestPilotTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - @svc = service(data, payload) - end - - def service(*args) - super Service::TestPilot, *args - end - - def data - { - 'token' => 'TOKEN' - } - end - - def test_reads_token_from_data - assert_equal "TOKEN", @svc.token - end - - def test_constructs_post_receive_url - assert_equal 'http://testpilot.me/callbacks/github', - @svc.test_pilot_url - end - - def test_posts_payload - @stubs.post '/callbacks/github' do |env| - assert_equal env[:params]['token'], @svc.token - assert_equal payload, JSON.parse(Rack::Utils.parse_query(env[:body])['payload']) - end - @svc.receive_push - end - - def test_it_raises_an_error_if_no_token_is_supplied - data = {'token' => ''} - svc = service(data, payload) - assert_raises Service::ConfigurationError do - svc.receive_push - end - end - - def test_strips_whitespace_from_form_values - data = { - 'token' => 'TOKEN ' - } - - svc = service(data, payload) - assert_equal 'TOKEN', svc.token - end -end - diff --git a/test/toggl_test.rb b/test/toggl_test.rb index 00d16915c..c02fef935 100644 --- a/test/toggl_test.rb +++ b/test/toggl_test.rb @@ -6,11 +6,11 @@ def setup end def test_push - url = "/api/v5/tasks.json" + url = "/api/v8/time_entries" @stubs.post url do |env| assert_equal 'www.toggl.com', env[:url].host assert_equal basic_auth(:a, :api_token), env[:request_headers]['authorization'] - assert_equal 900, JSON.parse(env[:body])['task']['duration'] + assert_equal 900, JSON.parse(env[:body])['time_entry']['duration'] [200, {}, ''] end diff --git a/test/trac_test.rb b/test/trac_test.rb index 79e5acbb6..445046d95 100644 --- a/test/trac_test.rb +++ b/test/trac_test.rb @@ -9,7 +9,7 @@ def test_push @stubs.post "/s/github/t" do |env| assert_equal 's.trac.com', env[:url].host assert_equal basic_auth(:u, :p), env[:request_headers]['authorization'] - data = Rack::Utils.parse_nested_query(env[:body]) + data = Faraday::Utils.parse_nested_query(env[:body]) assert_equal 1, JSON.parse(data['payload'])['a'] [200, {}, ''] end diff --git a/test/trajectory_test.rb b/test/trajectory_test.rb deleted file mode 100644 index 1d2cb4b7d..000000000 --- a/test/trajectory_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class TrajectoryTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - @stubs.post '/api/payloads?api_key=test_api_key' do |env| - assert_equal 'application/json', env[:request_headers]['Content-Type'] - [200, {}, ''] - end - - svc = service({'api_key' => 'test_api_key'}, payload) - svc.receive_push - - @stubs.verify_stubbed_calls - end - - def service(*args) - super Service::Trajectory, *args - end -end diff --git a/test/travis_test.rb b/test/travis_test.rb index 847731630..c50d49697 100644 --- a/test/travis_test.rb +++ b/test/travis_test.rb @@ -3,39 +3,69 @@ class TravisTest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new - @svc = service(data, payload) + @svc = service(basic_config, push_payload) + @svc.delivery_guid = 'guid-123' end - def test_reads_user_from_data + def test_reads_user_from_config assert_equal 'kronn', @svc.user end - def test_reads_token_from_data + def test_reads_token_from_config assert_equal "5373dd4a3648b88fa9acb8e46ebc188a", @svc.token end - def test_reads_domain_from_data - assert_equal "my-travis-ci.heroku.com", @svc.domain + def test_reads_domain_from_config + svc = service(basic_config.merge({ 'domain' => 'http://example.com' }), payload) + assert_equal "example.com", svc.domain end def test_keeps_https_scheme - svc = service(data.merge({'domain' => 'https://example.com'}), payload) + svc = service(basic_config.merge({ 'domain' => 'https://example.com' }), payload) assert_equal 'https', svc.scheme end + def test_reads_default_domain_when_not_in_config + assert_equal "notify.travis-ci.org", @svc.domain + end + def test_constructs_post_receive_url - assert_equal 'http://my-travis-ci.heroku.com/builds', - @svc.travis_url + assert_equal 'http://notify.travis-ci.org', @svc.travis_url end def test_posts_payload - @stubs.post '/builds' do |env| - assert_equal 'my-travis-ci.heroku.com', env[:url].host + @stubs.post '/' do |env| + assert_equal 'http://notify.travis-ci.org', env[:url].to_s + assert_equal basic_auth('kronn', '5373dd4a3648b88fa9acb8e46ebc188a'), + env[:request_headers]['authorization'] + assert_equal 'push', env[:request_headers]['x-github-event'] + assert_equal 'guid-123', env[:request_headers]['x-github-guid'] + assert_equal payload, JSON.parse(Faraday::Utils.parse_query(env[:body])['payload']) + end + @svc.receive_event + end + + def test_pull_request_payload + @svc = service(:pull_request, basic_config, pull_payload) + @stubs.post '/' do |env| + assert_equal 'http://notify.travis-ci.org', env[:url].to_s assert_equal basic_auth('kronn', '5373dd4a3648b88fa9acb8e46ebc188a'), env[:request_headers]['authorization'] - assert_equal payload, JSON.parse(Rack::Utils.parse_query(env[:body])['payload']) + assert_equal 'pull_request', env[:request_headers]['x-github-event'] + assert_equal pull_payload, JSON.parse(Faraday::Utils.parse_query(env[:body])['payload']) end - @svc.receive_push + @svc.receive_event + end + + def test_pull_request_payload_without_username + data = { + 'user' => '', + 'token' => '5373dd4a3648b88fa9acb8e46ebc188a' + } + svc = service(:pull_request, blank_user_config, pull_payload) + + assert_equal pull_payload['repository']['owner']['login'], svc.user + assert_equal '5373dd4a3648b88fa9acb8e46ebc188a', svc.token end def test_strips_whitespace_from_form_values @@ -46,6 +76,7 @@ def test_strips_whitespace_from_form_values } svc = service(data, payload) + assert_equal 'kronn', svc.user assert_equal '5373dd4a3648b88fa9acb8e46ebc188a', svc.token assert_equal 'my-travis-ci.heroku.com', svc.domain @@ -59,14 +90,15 @@ def test_handles_blank_strings_without_errors } svc = service(data, payload) + assert_equal 'mojombo', svc.user assert_equal '5373dd4a3648b88fa9acb8e46ebc188a', svc.token - assert_equal 'travis-ci.org', svc.domain + assert_equal 'notify.travis-ci.org', svc.domain assert_equal 'http', svc.scheme end def test_infers_user_from_repo_data - svc = service(data.reject{|key,v| key == 'user'}, payload) + svc = service(basic_config.reject{ |key,v| key == 'user' }, payload) assert_equal "mojombo", svc.user end @@ -74,73 +106,26 @@ def test_defaults_to_http_scheme assert_equal 'http', @svc.scheme end - def test_defaults_to_travis_ci_domain - svc = service(data.reject{|key,v| key == 'domain'}, payload) - assert_equal "travis-ci.org", svc.domain + def test_defaults_to_pings_travis_ci_domain + svc = service(basic_config.reject{ |key,v| key == 'domain' }, payload) + assert_equal "notify.travis-ci.org", svc.domain end def service(*args) super Service::Travis, *args end - def data + def basic_config { 'user' => 'kronn', - 'token' => '5373dd4a3648b88fa9acb8e46ebc188a', - 'domain' => 'my-travis-ci.heroku.com' + 'token' => '5373dd4a3648b88fa9acb8e46ebc188a' } end - def payload2 + + def blank_user_config { - "after" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", - "ref" => "refs/heads/master", - "before" => "4c8124ffcf4039d292442eeccabdeca5af5c5017", - "compare" => "http://github.com/mojombo/grit/compare/4c8124ffcf4039d292442eeccabdeca5af5c5017...a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", - "forced" => false, - "created" => false, - "deleted" => false, - - "repository" => { - "name" => "grit", - "url" => "http://github.com/mojombo/grit", - "owner" => { "name" => "mojombo", "email" => "tom@mojombo.com" } - }, - - "commits" => [ - { - "distinct" => true, - "removed" => [], - "message" => "[#WEB-249 status:31 resolution:1] stub git call for Grit#heads test", - "added" => [], - "timestamp" => "2007-10-10T00:11:02-07:00", - "modified" => ["lib/grit/grit.rb", "test/helper.rb", "test/test_grit.rb"], - "url" => "http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325", - "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, - "id" => "06f63b43050935962f84fe54473a7c5de7977325" - }, - { - "distinct" => true, - "removed" => [], - "message" => "clean up heads test", - "added" => [], - "timestamp" => "2007-10-10T00:18:20-07:00", - "modified" => ["test/test_grit.rb"], - "url" => "http://github.com/mojombo/grit/commit/5057e76a11abd02e83b7d3d3171c4b68d9c88480", - "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, - "id" => "5057e76a11abd02e83b7d3d3171c4b68d9c88480" - }, - { - "distinct" => true, - "removed" => [], - "message" => "add more comments throughout", - "added" => [], - "timestamp" => "2007-10-10T00:50:39-07:00", - "modified" => ["lib/grit.rb", "lib/grit/commit.rb", "lib/grit/grit.rb"], - "url" => "http://github.com/mojombo/grit/commit/a47fd41f3aa4610ea527dcc1669dfdb9c15c5425", - "author" => { "name" => "Tom Preston-Werner", "email" => "tom@mojombo.com" }, - "id" => "a47fd41f3aa4610ea527dcc1669dfdb9c15c5425" - } - ] + 'user' => '', + 'token' => '5373dd4a3648b88fa9acb8e46ebc188a' } end end diff --git a/test/trello_test.rb b/test/trello_test.rb new file mode 100644 index 000000000..d43817e2a --- /dev/null +++ b/test/trello_test.rb @@ -0,0 +1,152 @@ +require File.expand_path('../helper', __FILE__) + +class TrelloTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @data = {'push_list_id' => 'abc123', 'consumer_token' => 'blarg', 'master_only' => 1} + end + + def test_push + svc = service :push, @data + + def svc.message_max_length; 4 end + + @stubs.post "/1/cards" do |env| + assert_equal 'api.trello.com', env[:url].host + assert_match 'token=blarg', env[:body] + assert_match 'idList=abc123', env[:body] + [200, {}, ''] + end + + assert_equal 'stub...', svc.send(:name_for_commit, svc.payload['commits'].first) + + assert_equal correct_description, svc.send(:desc_for_commit, svc.payload['commits'].first) + + svc.receive_push + end + + def test_backward_compatible_push_list_id + @data['list_id'] = @data['push_list_id'] + @data.delete 'push_list_id' + svc = service :push, @data + assert_cards_created svc + end + + def test_master_only_no_master + svc = service :push, + @data.update("master_only" => 1), + payload.update("ref" => "refs/heads/non-master") + + assert_no_cards_created svc + end + + def test_master_only_master + svc = service :push, @data.update("master_only" => 1) + assert_cards_created svc + end + + def test_ignore_regex + svc = service :push, @data.merge!("ignore_regex" => "Grit|throughout|heads") + assert_no_cards_created svc + end + + def test_ignore_regex_timeout + push_payload = Service::PushHelpers.sample_payload + push_payload["commits"].first.merge!("message" => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZ") + svc = service @data.merge!("ignore_regex" => "(a+)+$"), push_payload + + assert_raises(Service::TimeoutError) do + call_hook_on_service svc, :push + end + end + + def test_no_ignore_regex + svc = service :push, @data.merge!("ignore_regex" => "") + assert_cards_created svc + end + + def test_pull_request + svc = service :pull_request, @data.merge!("pull_request_list_id" => 'zxy987') + assert_cards_created svc, :pull_request + end + + def test_closed_pull_request + svc = service :pull_request, + @data.merge!("pull_request_list_id" => 'zxy987'), + pull_payload.merge!("action" => "closed") + assert_no_cards_created svc, :pull_request + end + + def test_comment_on_card + payload = { 'commits' => [{ 'message' => commit_message, + 'author' => { 'name' => 'John Doe' }, + 'url' => 'http://github.com/commit' }], + 'repository' => { 'name' => 'whatever' }, + 'ref' => 'refs/heads/master' } + svc = service :push, @data, payload + assert_commented('abc123') + assert_commented('abc456') + @stubs.post("/1/cards") { [200, {}, ''] } + svc.receive_push + @stubs.verify_stubbed_calls + end + + private + + def call_hook_on_service svc, method + case method + when :push + svc.receive_push + when :pull_request + svc.receive_pull_request + end + end + + def assert_commented(card_id) + @stubs.post "/1/cards/#{card_id}/actions/comments" do |env| + assert_equal 'api.trello.com', env[:url].host + assert_match 'text=' + CGI.escape('John Doe added commit http://github.com/commit'), env[:body] + [200, {}, ''] + end + end + + def assert_cards_created(svc, method = :push) + @stubs.post "/1/cards" do |env| + assert_equal 'api.trello.com', env[:url].host + [200, {}, ''] + end + call_hook_on_service svc, method + end + + def assert_no_cards_created(svc, method = :push) + @stubs.post "/1/cards" do + raise "This should not be called" + end + call_hook_on_service svc, method + end + + def correct_description + 'Author: Tom Preston-Werner + +http://github.com/mojombo/grit/commit/06f63b43050935962f84fe54473a7c5de7977325 + +Repo: grit + +Branch: master + +Commit Message: stub git call for Grit#heads test f:15 Case#1' + end + + def service(*args) + super Service::Trello, *args + end + + def commit_message + <<-EOT +Example message + +Fixes https://trello.com/c/abc123 +Closes https://trello.com/c/abc456/longer-url +EOT + end +end diff --git a/test/twitter_test.rb b/test/twitter_test.rb index 27856a980..019932a6c 100644 --- a/test/twitter_test.rb +++ b/test/twitter_test.rb @@ -1,6 +1,8 @@ require File.expand_path('../helper', __FILE__) class TwitterTest < Service::TestCase + TWITTER_SHORT_URL_LENGTH_HTTPS = 23 + def test_push svc = service({'token' => 't', 'secret' => 's'}, payload) @@ -29,9 +31,125 @@ def test_oauth_consumer assert svc.consumer end + def test_tweet_length + p = payload + p['commits'][0]['message']="This is a very long message specifically designed to test the new behaviour of the twitter service hook with extremely long tweets. As should be happening now." + svc = service({'token' => 't', 'secret' => 's'}, p) + + def svc.statuses + @statuses ||= [] + end + + def svc.post(status) + statuses << status + end + + svc.receive_push + + svc.statuses.each do |st| + st = st.gsub(/http[^ ]+/, "a"*TWITTER_SHORT_URL_LENGTH_HTTPS) # replace the URL with a substitute for the shortened one + assert st.length<=140 + end + end + + # Make sure that whitespace in the original commit message is preserved + def test_whitespace + p = payload + p['commits'][0]['message']="message \nwith\n\n weird whitespace " + svc = service({'token' => 't', 'secret' => 's'}, p) + + def svc.statuses + @statuses ||= [] + end + + def svc.post(status) + statuses << status + end + + svc.receive_push + assert_match p['commits'][0]['message'], svc.statuses[0] + end + + # Make sure that asterisks in the original commit message are replaced + def test_asterisk_replace + p = payload + p['commits'][0]['message']="message * with * stars" + svc = service({'token' => 't', 'secret' => 's'}, p) + + def svc.statuses + @statuses ||= [] + end + + def svc.post(status) + statuses << status + end + + svc.receive_push + assert_match "message οΉ‘ with οΉ‘ stars", svc.statuses[0] + end + + # Make sure that GitHub @mentions are injected with a zero-width space + # so that they don't turn into (potentially unmatching) twitter @mentionds + def test_mentions + p = payload + p['commits'][0]['message']="This commit was done by @sgolemon" + p['commits'][1]['message']="@sgolemon committed this" + p['commits'][2]['message']="@sgolemon made a @ @\ttest for @kdaigle" + svc = service({'token' => 't', 'secret' => 's'}, p) + + def svc.statuses + @statuses ||= [] + end + + def svc.post(status) + statuses << status + end + + svc.receive_push + assert_equal 3, svc.statuses.size + svc.statuses.each do |st| + # Any @ which is followed by a word character is an error + assert !st.match('@(?=[[:word:]])') + # Any @ which is followed by a U+200b ZERO WIDTH SPACE but not a word + # character is an error + assert !st.match('@(?=\u200b[^[:word:]])') + end + end + def service(*args) super Service::Twitter, *args end -end + def test_filter_branch + svc = service({'token' => 't', 'secret' => 's', 'filter_branch' => 'tweet' }, payload) + + def svc.shorten_url(*args) 'short' end + def svc.statuses + @statuses ||= [] + end + def svc.post(status) + statuses << status + end + + svc.receive_push + assert_equal 0, svc.statuses.size + end + + def test_filter_branch_partial + svc = service({'token' => 't', 'secret' => 's', 'filter_branch' => 'ast' }, payload) + + def svc.shorten_url(*args) 'short' end + def svc.statuses + @statuses ||= [] + end + + def svc.post(status) + statuses << status + end + + svc.receive_push + assert_equal 3, svc.statuses.size + end + +end diff --git a/test/typetalk_test.rb b/test/typetalk_test.rb new file mode 100644 index 000000000..5e09b6249 --- /dev/null +++ b/test/typetalk_test.rb @@ -0,0 +1,62 @@ +require File.expand_path('../helper', __FILE__) + +class TypetalkTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @stubs.post "/oauth2/access_token" do |env| + form = Faraday::Utils.parse_query(env[:body]) + assert_equal 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', form['client_id'] + assert_equal 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', form['client_secret'] + assert_equal 'client_credentials', form['grant_type'] + assert_equal 'topic.post', form['scope'] + [200, {}, '{ "access_token": "TestToken" }'] + end + end + + def test_push + @stubs.post "/api/v1/topics/1" do |env| + form = Faraday::Utils.parse_query(env[:body]) + headers = env[:request_headers] + assert_equal 'Bearer TestToken', headers['Authorization'] + assert_equal "dragon3 has pushed 2 commit(s) to master at dragon3/github-services\nhttps://github.com/dragon3/github-services/compare/06f63b43050935962f84fe54473a7c5de7977325...06f63b43050935962f84fe54473a7c5de7977326", form['message'] + [200, {}, ''] + end + + svc = service(:push, {'client_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'client_secret' => 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'topic' => '1'}, payload_for_push_test) + svc.receive_push + end + + def test_pull_request + @stubs.post "/api/v1/topics/1" do |env| + form = Faraday::Utils.parse_query(env[:body]) + headers = env[:request_headers] + assert_equal 'Bearer TestToken', headers['Authorization'] + assert_equal "defunkt opened pull request #5: booya\nhttps://github.com/mojombo/magik/pulls/5", form['message'] + [200, {}, ''] + end + svc = service(:pull_request, {'client_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + 'client_secret' => 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + 'topic' => '1'}, pull_payload) + svc.receive_pull_request + end + + def service(*args) + super Service::Typetalk, *args + end + + def payload_for_push_test + { + 'ref' => 'refs/heads/master', + 'compare' => 'https://github.com/dragon3/github-services/compare/06f63b43050935962f84fe54473a7c5de7977325...06f63b43050935962f84fe54473a7c5de7977326', + 'pusher' => { 'name' => 'dragon3', }, + 'commits' => [ + {'id' => '06f63b43050935962f84fe54473a7c5de7977325'}, + {'id' => '06f63b43050935962f84fe54473a7c5de7977326'}], + 'repository' => {'name' => 'dragon3/github-services'}, + } + end + +end + diff --git a/test/web_test.rb b/test/web_test.rb index 143cfb983..deee14054 100644 --- a/test/web_test.rb +++ b/test/web_test.rb @@ -13,12 +13,13 @@ def test_push @stubs.post "/foo/" do |env| assert_equal 'push', env[:request_headers]['x-github-event'] + assert_match /^guid\-0\.\d+$/, env[:request_headers]['x-github-delivery'] assert_equal 'Basic bW9ua2V5OnNlY3JldA==', env[:request_headers]['authorization'] assert_match /form/, env[:request_headers]['content-type'] assert_equal 'abc.com', env[:url].host - params = Rack::Utils.parse_nested_query(env[:url].query) + params = Faraday::Utils.parse_nested_query(env[:url].query) assert_equal({'a' => '1'}, params) - body = Rack::Utils.parse_nested_query(env[:body]) + body = Faraday::Utils.parse_nested_query(env[:body]) assert_equal '1', body['a'] recv = JSON.parse(body['payload']) assert_equal payload, recv @@ -57,7 +58,7 @@ def test_push_with_secret assert_equal 'sha1='+OpenSSL::HMAC.hexdigest(Service::Web::HMAC_DIGEST, 'monkey', env[:body]), env[:request_headers]['X-Hub-Signature'] - body = Rack::Utils.parse_nested_query(env[:body]) + body = Faraday::Utils.parse_nested_query(env[:body]) recv = JSON.parse(body['payload']) assert_equal payload, recv [200, {}, ''] @@ -74,7 +75,7 @@ def test_push_as_json @stubs.post "/foo" do |env| assert_equal 'Basic bW9ua2V5OnNlY3JldA==', env[:request_headers]['authorization'] - params = Rack::Utils.parse_nested_query(env[:url].query) + params = Faraday::Utils.parse_nested_query(env[:url].query) assert_equal({'a' => '1'}, params) assert_match /json/, env[:request_headers]['content-type'] assert_equal 'abc.com', env[:url].host @@ -107,8 +108,31 @@ def test_push_as_json_with_secret svc.receive_event end + def test_log_data + data = { + 'url' => 'http://user:pass@abc.com/def', + 'secret' => 'monkey', + 'content_type' => 'json' + } + + svc = service(data, payload) + assert_equal 2, svc.log_data.size, svc.log_data.inspect + assert_equal 'http://user:********@abc.com/def', svc.log_data['url'] + assert_equal data['content_type'], svc.log_data['content_type'] + end + + def test_log_message + data = { + 'url' => 'http://abc.com/def', + 'secret' => 'monkey', + 'content_type' => 'json' + } + + svc = service(data, payload) + assert_match /^\[[^\]]+\] 200 web\/push \{/, svc.log_message(200) + end + def service(*args) super Service::Web, *args end end - diff --git a/test/web_translate_it_test.rb b/test/web_translate_it_test.rb index 0cffa4ad3..98593f82d 100644 --- a/test/web_translate_it_test.rb +++ b/test/web_translate_it_test.rb @@ -8,7 +8,7 @@ def setup def test_push @stubs.post "/api/projects/a/refresh_files" do |env| assert_equal 'webtranslateit.com', env[:url].host - data = Rack::Utils.parse_nested_query(env[:body]) + data = Faraday::Utils.parse_nested_query(env[:body]) assert_equal 1, JSON.parse(data['payload'])['a'] [200, {}, ''] end diff --git a/test/weblate_test.rb b/test/weblate_test.rb new file mode 100644 index 000000000..9d9786f5e --- /dev/null +++ b/test/weblate_test.rb @@ -0,0 +1,24 @@ +require File.expand_path('../helper', __FILE__) + +class WeblateTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + end + + def test_push + @stubs.post "/hooks/github/" do |env| + assert_equal 'weblate.example.org', env[:url].host + assert_equal 'application/x-www-form-urlencoded', + env[:request_headers]['content-type'] + [200, {}, ''] + end + + svc = service :push, + {'url' => 'weblate.example.org'}, payload + svc.receive_push + end + + def service(*args) + super Service::Weblate, *args + end +end diff --git a/test/windowsazure_test.rb b/test/windowsazure_test.rb new file mode 100644 index 000000000..7fb21288f --- /dev/null +++ b/test/windowsazure_test.rb @@ -0,0 +1,40 @@ +require File.expand_path("../helper", __FILE__) + +class WindowsAzureTest < Service::TestCase + include Service::HttpTestMethods + + def test_push + + data = { + "hostname" => "test.scm.azurewebsites.net", + "username" => "test_user", + "password" => "test_pwd" + } + + svc = service(data, payload) + + @stubs.post "/deploy?scmType=GitHub" do |env| + assert_equal 'push', env[:request_headers]['x-github-event'] + assert_equal 'Basic dGVzdF91c2VyOnRlc3RfcHdk', env[:request_headers]['authorization'] + assert_equal env[:url].host, data['hostname'] + assert_match /form/, env[:request_headers]['content-type'] + params = Faraday::Utils.parse_nested_query(env[:url].query) + assert_equal({'scmType' => 'GitHub'}, params) + body = Faraday::Utils.parse_nested_query(env[:body]) + assert_equal 'GitHub', body['scmType'] + recv = JSON.parse(body['payload']) + assert_equal payload, recv + assert_nil env[:request_headers]['X-Hub-Signature'] + assert_equal 'GitHub', body['scmType'] + [200, {}, ""] + end + + svc.receive_event + end + + def service_class + Service::WindowsAzure + end + +end + diff --git a/test/xmpp_im_test.rb b/test/xmpp_im_test.rb new file mode 100644 index 000000000..b6a85e6c7 --- /dev/null +++ b/test/xmpp_im_test.rb @@ -0,0 +1,383 @@ +class XmppImTest < Service::TestCase + class MockXmpp4r + + def send(message) + @messages = [] if @messages.nil? + @messages.push message + end + + def get_messages + @messages + end + + def connect(host, port) + @host = host + @port = port + end + + def get_host + @host + end + + def get_port + @port + end + + def close + + end + + end + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @config = { + 'JID' => 'me@example.com', + 'password' => 'password', + 'receivers' => 'bare@server full@server/resource', + 'notify_fork' => '1', + 'notify_wiki' => '1', + 'notify_comments' => '1', + 'notify_watch' => '1', + 'notify_issue' => '1', + 'notify_pull' => '1', + 'is_test' => true + } + @mock = MockXmpp4r.new() + end + + def service(*args) + xmppIm = super Service::XmppIm, *args + xmppIm.set_connection @mock + xmppIm + end + + def test_no_jid_provided + assert_raises(Service::ConfigurationError, 'JID is required') do + config = @config + config['JID'] = '' + service(config, payload).receive_event + end + end + + def test_no_password_provided + assert_raises(Service::ConfigurationError, 'Password is required') do + config = @config + config['password'] = '' + service(config, payload).receive_event + end + end + + def illegal_jid_throws_error + assert_raises(Service::ConfigurationError, 'Illegal receiver JID') do + config = @config + config['receivers'] = 'bare@server illegal@server@what?' + service(config, payload).receive_event + end + end + + def test_errors_on_bad_port + assert_raises(Service::ConfigurationError, 'XMPP port must be numeric') do + config = @config + config['port'] = 'PORT NUMBER' + service(config, payload).receive_event + end + end + + def sets_custom_port + config = @config + port = '1234' + config['port'] = port + service(config, payload).receive_event + assert_equal(Integer(port), @mock.get_port) + end + + def sets_custom_host + config = @config + host = 'github.com' + config['host'] = host + service(config, payload).receive_event + assert_equal(host, @mock.get_host) + end + + def uses_default_host + config = @config + service(config, payload).receive_event + assert_true(@mock.get_host.nil?) + end + + def uses_default_port + config = @config + service(config, payload).receive_event + assert_equal(5222, @mock.get_port) + end + + def test_returns_false_if_not_on_filtered_branch + config = @config + config['filter_branch'] = 'development' + assert_equal( + false, + service(config, payload).receive_event, + 'Should have filtered by branch' + ) + end + + def test_returns_true_if_part_matched_filtered_branch + config = @config + config['filter_branch'] = 'ast' + assert_equal( + true, + service(config, payload).receive_event, + 'Should not have filtered this branch' + ) + end + + def test_returns_false_if_fork_event_and_not_notifiying + config = @config + config['notify_fork'] = '0' + assert_equal( + false, + service(:fork, config, payload).receive_event, + 'Should not reported fork event' + ) + end + + def test_returns_false_if_watch_event_and_not_notifiying + config = @config + config['notify_watch'] = '0' + assert_equal( + false, + service(:watch, config, payload).receive_event, + 'Should not reported watch event' + ) + end + + def test_returns_false_if_comment_event_and_not_notifiying + config = @config + config['notify_comments'] = '0' + assert_equal( + false, + service(:issue_comment, config, payload).receive_event, + 'Should not reported comment event' + ) + end + + def test_returns_false_if_wiki_event_and_not_notifiying + config = @config + config['notify_wiki'] = '0' + assert_equal( + false, + service(:gollum, config, payload).receive_event, + 'Should not reported wiki event' + ) + end + + def test_returns_false_if_issue_event_and_not_notifiying + config = @config + config['notify_issue'] = '0' + assert_equal( + false, + service(:issues, config, payload).receive_event, + 'Should not reported issues event' + ) + end + + def test_returns_false_if_pull_event_and_not_notifiying + config = @config + config['notify_pull'] = '0' + assert_equal( + false, + service(:pull_request_review_comment, config, payload).receive_event, + 'Should not reported pull event' + ) + end + + def test_generates_expected_push_message + config = @config + message = '' + service(:push, config, payload).receive_event + assert_equal( + 8, + @mock.get_messages().length, + 'Expected 8 messages' + ) + assert_equal( + "[grit] @rtomayko pushed 3 new commits to master: http://github.com/mojombo/grit/compare/4c8124f...a47fd41", + @mock.get_messages()[0].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] stub git call for Grit#heads test f:15 Case#1 - Tom Preston-Werner", + @mock.get_messages()[1].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] clean up heads test f:2hrs - Tom Preston-Werner", + @mock.get_messages()[2].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] add more comments throughout - Tom Preston-Werner", + @mock.get_messages()[3].body, + 'Expected push message not received' + ) + end + + def test_generates_error_if_push_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:commit_comment, @config, {}).receive_event + end + end + + def test_sends_messages_to_expected_jids + service(:commit_comment, @config, commit_comment_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + ::Jabber::JID.new('full@server/resource'), + @mock.get_messages()[1].to + ) + assert_equal( + ::Jabber::JID.new('bare@server'), + @mock.get_messages()[0].to + ) + end + + def test_generates_expected_commit_comment_message + message = '[grit] @defunkt commented on commit 441e568: this... https://github.com/mojombo/magik/commit/441e5686a726b79bcdace639e2591a60718c9719#commitcomment-3332777' + service(:commit_comment, @config, commit_comment_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected commit comment message not received' + ) + end + + def test_generates_error_if_commit_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:commit_comment, @config, {}).receive_event + end + end + + def test_generates_expected_issue_comment_message + message = '[grit] @defunkt commented on issue #5: this... ' + service(:issue_comment, @config, issue_comment_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected issue comment message not received' + ) + end + + def test_generates_error_if_issue_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:issue_comment, @config, {}).receive_event + end + end + + def test_generates_expected_issues_message + message = '[grit] @defunkt opened issue #5: booya ' + service(:issues, @config, issues_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected issues message not received' + ) + end + + def test_generates_error_if_issues_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:issues, @config, {}).receive_event + end + end + + def test_generates_expected_pull_request_message + message = '[grit] @defunkt opened pull request #5: booya (master...feature) https://github.com/mojombo/magik/pulls/5' + service(:pull_request, @config, pull_request_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected pull request message not received' + ) + end + + def test_generates_error_if_pull_request_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + payload = pull_request_payload + payload['pull_request']['base'] = {} + service(:pull_request, @config, payload).receive_event + end + end + + def test_generates_expected_pull_request_review_comment_message + message = '[grit] @defunkt commented on pull request #5 03af7b9: very... https://github.com/mojombo/magik/pull/5#discussion_r18785396' + service(:pull_request_review_comment, @config, pull_request_review_comment_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected pull request review comment message not received' + ) + end + + def test_generates_error_if_pull_request_review_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:pull_request_review_comment, @config, {}).receive_event + end + end + + def test_generates_expected_gollum_message + message = '[grit] @defunkt modified 1 page https://github.com/mojombo/magik/wiki/Foo' + service(:gollum, @config, gollum_payload).receive_event + assert_equal( + 4, + @mock.get_messages().length, + 'Expected 4 messages' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected wiki edit summmary message not received' + ) + assert_equal( + 'User created page "Foo" https://github.com/mojombo/magik/wiki/Foo', + @mock.get_messages()[1].body, + 'Expected wiki page edit not received' + ) + end + + def test_generates_error_if_gollum_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:gollum, @config, {}).receive_event + end + end + +end diff --git a/test/xmpp_muc_test.rb b/test/xmpp_muc_test.rb new file mode 100644 index 000000000..6f0691a6a --- /dev/null +++ b/test/xmpp_muc_test.rb @@ -0,0 +1,377 @@ +class XmppMucTest < Service::TestCase + class MockXmpp4r + + def send(message) + @messages = [] if @messages.nil? + @messages.push message + end + + def get_messages + @messages + end + + def connect(host, port) + @host = host + @port = port + end + + def get_host + @host + end + + def get_port + @port + end + + def exit + + end + + end + + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + + @config = { + 'JID' => 'me@example.com', + 'password' => 'password', + 'room' => 'status', + 'server' => 'muc.example.com', + 'nickname' => 'github', + 'notify_fork' => '1', + 'notify_wiki' => '1', + 'notify_comments' => '1', + 'notify_watch' => '1', + 'notify_issue' => '1', + 'notify_pull' => '1', + 'is_test' => true + } + @mock = MockXmpp4r.new() + end + + def service(*args) + xmppMuc = super Service::XmppMuc, *args + xmppMuc.set_muc_connection @mock + xmppMuc + end + + def test_no_jid_provided + assert_raises(Service::ConfigurationError, 'JID is required') do + config = @config + config['JID'] = '' + service(config, payload).receive_event + end + end + + def test_no_password_provided + assert_raises(Service::ConfigurationError, 'Password is required') do + config = @config + config['password'] = '' + service(config, payload).receive_event + end + end + + def test_no_room_provided + assert_raises(Service::ConfigurationError, 'Room is required') do + config = @config + config['room'] = '' + service(config, payload).receive_event + end + end + + def test_no_server_provided + assert_raises(Service::ConfigurationError, 'Server is required') do + config = @config + config['server'] = '' + service(config, payload).receive_event + end + end + + def test_errors_on_bad_port + assert_raises(Service::ConfigurationError, 'XMPP port must be numeric') do + config = @config + config['port'] = 'PORT NUMBER' + service(config, payload).receive_event + end + end + + + def sets_custom_port + config = @config + port = '1234' + config['port'] = port + service(config, payload).receive_event + assert_equal(Integer(port), @mock.get_port) + end + + def sets_custom_host + config = @config + host = 'github.com' + config['host'] = host + service(config, payload).receive_event + assert_equal(host, @mock.get_host) + end + + def uses_default_host + config = @config + service(config, payload).receive_event + assert_true(@mock.get_host.nil?) + end + + def uses_default_port + config = @config + service(config, payload).receive_event + assert_equal(5222, @mock.get_port) + end + + def test_returns_false_if_not_on_filtered_branch + config = @config + config['filter_branch'] = 'development' + assert_equal( + false, + service(config, payload).receive_event, + 'Should have filtered by branch' + ) + end + + def test_returns_true_if_part_matched_filtered_branch + config = @config + config['filter_branch'] = 'ast' + assert_equal( + true, + service(config, payload).receive_event, + 'Should not have filtered this branch' + ) + end + + def test_returns_false_if_fork_event_and_not_notifiying + config = @config + config['notify_fork'] = '0' + assert_equal( + false, + service(:fork, config, payload).receive_event, + 'Should not reported fork event' + ) + end + + def test_returns_false_if_watch_event_and_not_notifiying + config = @config + config['notify_watch'] = '0' + assert_equal( + false, + service(:watch, config, payload).receive_event, + 'Should not reported watch event' + ) + end + + def test_returns_false_if_comment_event_and_not_notifiying + config = @config + config['notify_comments'] = '0' + assert_equal( + false, + service(:issue_comment, config, payload).receive_event, + 'Should not reported comment event' + ) + end + + def test_returns_false_if_wiki_event_and_not_notifiying + config = @config + config['notify_wiki'] = '0' + assert_equal( + false, + service(:gollum, config, payload).receive_event, + 'Should not reported wiki event' + ) + end + + def test_returns_false_if_issue_event_and_not_notifiying + config = @config + config['notify_issue'] = '0' + assert_equal( + false, + service(:issues, config, payload).receive_event, + 'Should not reported issues event' + ) + end + + def test_returns_false_if_pull_event_and_not_notifiying + config = @config + config['notify_pull'] = '0' + assert_equal( + false, + service(:pull_request_review_comment, config, payload).receive_event, + 'Should not reported pull event' + ) + end + + def test_generates_expected_push_message + config = @config + message = '' + service(:push, config, payload).receive_event + assert_equal( + 4, + @mock.get_messages().length, + 'Expected 4 messages' + ) + assert_equal( + "[grit] @rtomayko pushed 3 new commits to master: http://github.com/mojombo/grit/compare/4c8124f...a47fd41", + @mock.get_messages()[0].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] stub git call for Grit#heads test f:15 Case#1 - Tom Preston-Werner", + @mock.get_messages()[1].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] clean up heads test f:2hrs - Tom Preston-Werner", + @mock.get_messages()[2].body, + 'Expected push message not received' + ) + assert_equal( + "[grit/master] add more comments throughout - Tom Preston-Werner", + @mock.get_messages()[3].body, + 'Expected push message not received' + ) + end + + def test_generates_error_if_push_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:commit_comment, @config, {}).receive_event + end + end + + def test_generates_expected_commit_comment_message + message = '[grit] @defunkt commented on commit 441e568: this... https://github.com/mojombo/magik/commit/441e5686a726b79bcdace639e2591a60718c9719#commitcomment-3332777' + service(:commit_comment, @config, commit_comment_payload).receive_event + assert_equal( + 1, + @mock.get_messages().length, + 'Expected 1 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected commit comment message not received' + ) + end + + def test_generates_error_if_commit_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:commit_comment, @config, {}).receive_event + end + end + + def test_generates_expected_issue_comment_message + message = '[grit] @defunkt commented on issue #5: this... ' + service(:issue_comment, @config, issue_comment_payload).receive_event + assert_equal( + 1, + @mock.get_messages().length, + 'Expected 1 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected issue comment message not received' + ) + end + + def test_generates_error_if_issue_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:issue_comment, @config, {}).receive_event + end + end + + def test_generates_expected_issues_message + message = '[grit] @defunkt opened issue #5: booya ' + service(:issues, @config, issues_payload).receive_event + assert_equal( + 1, + @mock.get_messages().length, + 'Expected 1 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected issues message not received' + ) + end + + def test_generates_error_if_issues_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:issues, @config, {}).receive_event + end + end + + def test_generates_expected_pull_request_message + message = '[grit] @defunkt opened pull request #5: booya (master...feature) https://github.com/mojombo/magik/pulls/5' + service(:pull_request, @config, pull_request_payload).receive_event + assert_equal( + 1, + @mock.get_messages().length, + 'Expected 1 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected pull request message not received' + ) + end + + def test_generates_error_if_pull_request_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + payload = pull_request_payload + payload['pull_request']['base'] = {} + service(:pull_request, @config, payload).receive_event + end + end + + def test_generates_expected_pull_request_review_comment_message + message = '[grit] @defunkt commented on pull request #5 03af7b9: very... https://github.com/mojombo/magik/pull/5#discussion_r18785396' + service(:pull_request_review_comment, @config, pull_request_review_comment_payload).receive_event + assert_equal( + 1, + @mock.get_messages().length, + 'Expected 1 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected pull request review comment message not received' + ) + end + + def test_generates_error_if_pull_request_review_comment_message_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:pull_request_review_comment, @config, {}).receive_event + end + end + + def test_generates_expected_gollum_message + message = '[grit] @defunkt modified 1 page https://github.com/mojombo/magik/wiki/Foo' + service(:gollum, @config, gollum_payload).receive_event + assert_equal( + 2, + @mock.get_messages().length, + 'Expected 2 message' + ) + assert_equal( + message, + @mock.get_messages()[0].body, + 'Expected wiki edit summmary message not received' + ) + assert_equal( + 'User created page "Foo" https://github.com/mojombo/magik/wiki/Foo', + @mock.get_messages()[1].body, + 'Expected wiki page edit not received' + ) + end + + def test_generates_error_if_gollum_cant_be_generated + assert_raises(Service::ConfigurationError, /Unable to build message/) do + service(:gollum, @config, {}).receive_event + end + end + +end diff --git a/test/yammer_test.rb b/test/yammer_test.rb deleted file mode 100644 index c5816ee3e..000000000 --- a/test/yammer_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require File.expand_path('../helper', __FILE__) - -class YammerTest < Service::TestCase - def setup - @stubs = Faraday::Adapter::Test::Stubs.new - end - - def test_push - svc = service({'group_id' => 'g'}, payload) - - def svc.messages - @messages ||= [] - end - - def svc.send_message(params) - messages << params - end - - def svc.shorten_url(*args) - 'short' - end - - svc.receive_push - - assert_equal 3, svc.messages.size - assert_equal %w(g g g), svc.messages.map { |m| m['group_id'] } - end - - def service(*args) - super Service::Yammer, *args - end -end - - diff --git a/test/youtrack_test.rb b/test/youtrack_test.rb index b65549118..9ff62d7c1 100644 --- a/test/youtrack_test.rb +++ b/test/youtrack_test.rb @@ -3,9 +3,11 @@ class YouTrackTest < Service::TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new + @data = {'base_url' => 'http://yt.com/abc', 'committers' => 'c', + 'username' => 'u', 'password' => 'p'} end - def test_push + def valid_process_stubs @stubs.post "/abc/rest/user/login" do |env| assert_equal 'yt.com', env[:url].host assert_equal 'u', env[:params]["login"] @@ -27,6 +29,10 @@ def test_push assert_equal 'sc', env[:request_headers]['Cookie'] [200, {}, %()] end + end + + def valid_process_stubs_case_1 + valid_process_stubs @stubs.post "/abc/rest/issue/case-1/execute" do |env| assert_equal 'yt.com', env[:url].host @@ -35,17 +41,133 @@ def test_push assert_equal 'mojombo', env[:params]['runAs'] [200, {}, ''] end + end + + + + def test_push + valid_process_stubs_case_1 hash = payload hash['commits'].first['message'].sub! /Case#1/, '#case-1 zomg omg' - svc = service({'base_url' => 'http://yt.com/abc', 'committers' => 'c', - 'username' => 'u', 'password' => 'p'}, hash) + svc = service(@data, hash) svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_push_no_command + valid_process_stubs + + @stubs.post "/abc/rest/issue/case-2/execute" do |env| + assert_equal 'yt.com', env[:url].host + assert_equal 'sc', env[:request_headers]['Cookie'] + assert_equal 'comment', env[:params]['command'] + assert_equal 'mojombo', env[:params]['runAs'] + [200, {}, ''] + end + + hash = payload + hash['commits'].first['message'].sub! /Case#1/, '#case-2' + + svc = service(@data, hash) + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_branch_match + valid_process_stubs + + @stubs.post "/abc/rest/issue/case-2/execute" do |env| + assert_equal 'yt.com', env[:url].host + assert_equal 'sc', env[:request_headers]['Cookie'] + assert_equal 'comment', env[:params]['command'] + assert_equal 'mojombo', env[:params]['runAs'] + [200, {}, ''] + end + + hash = payload + hash['commits'].first['message'].sub! /Case#1/, '#case-2!! zomg omg' + hash['ref'] = 'refs/heads/master' + + svc = service(@data.merge({'branch' => 'master dev' }), hash) + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_branch_mismatch + payload = {'ref' => 'refs/heads/master'} + + svc = service({'base_url' => '', 'branch' => 'other'}, payload) + + # Missing payload settings would lead to an exception on processing. Processing + # should never happen with mismatched branches. + assert_nothing_raised { svc.receive_push } + end + + def test_process_not_distinct + valid_process_stubs_case_1 + + hash = payload + hash['commits'].each { |commit| + commit['distinct'] = false + } + hash['commits'].first['message'].sub! /Case#1/, '#case-1 zomg omg' + + svc = service(@data.merge({'process_distinct' => '0'}), hash) + + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_process_distinct + valid_process_stubs_case_1 + + hash = payload + hash['commits'].first['message'].sub! /Case#1/, '#case-1 zomg omg' + + svc = service(@data.merge({'process_distinct' => '1'}), hash) + + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_dont_process_not_distinct + + hash = payload + hash['commits'].each { |commit| + commit['distinct'] = false + } + hash['commits'].first['message'].sub! /Case#1/, '#case-1 zomg omg' + + svc = service(@data.merge({'process_distinct' => '1'}), hash) + + svc.receive_push + + @stubs.verify_stubbed_calls + end + + def test_pull_request_event + valid_process_stubs_case_1 + + hash = pull_payload + hash['action'] = 'closed' + hash['sender'] = { 'login' => 'Tom Preston-Werner', 'email' => 'tom@mojombo.com'} + hash['pull_request']['body'] = '#case-1 zomg omg' + + svc = service(@data, hash) + + svc.receive_pull_request + + @stubs.verify_stubbed_calls end def service(*args) super Service::YouTrack, *args end end - diff --git a/test/zendesk_test.rb b/test/zendesk_test.rb new file mode 100644 index 000000000..bd10b90eb --- /dev/null +++ b/test/zendesk_test.rb @@ -0,0 +1,50 @@ +require File.expand_path("../helper", __FILE__) + +class ZendeskTest < Service::TestCase + def setup + @stubs = Faraday::Adapter::Test::Stubs.new + @data = { "username" => "user", "password" => "pass", "subdomain" => "igor" } + @payload = { :message => "My name is zd#12345 what do you say?" } + end + + def test_subdomain + post + svc = service :event, @data, @payload + svc.receive_event + end + + def test_domain + post + + svc = service :event, @data.merge("subdomain" => "igor.zendesk.com"), @payload + svc.receive_event + end + + def test_unmatched_ticket + post + + svc = service :event, @data, { :message => "Nothing to match" } + svc.receive_event + + begin + @stubs.verify_stubbed_calls + rescue RuntimeError + else + assert_true false + end + end + + def post + @stubs.post "/api/v2/integrations/github?ticket_id=12345" do |env| + assert_equal "application/json", env[:request_headers]["Content-Type"] + assert_equal "igor.zendesk.com", env[:url].host + assert_equal "12345", env[:params]["ticket_id"] + assert_equal JSON.generate({ :payload => @payload }), env[:body] + [ 201, {}, "" ] + end + end + + def service(*args) + super Service::Zendesk, *args + end +end diff --git a/test/zohoprojects_test.rb b/test/zohoprojects_test.rb index 6ac909503..c66620fcb 100644 --- a/test/zohoprojects_test.rb +++ b/test/zohoprojects_test.rb @@ -16,14 +16,14 @@ def test_push url = "/serviceHook" @stubs.post url do |env| assert_equal 'projects.zoho.com', env[:url].host - params = Rack::Utils.parse_query env[:body] + params = Faraday::Utils.parse_query env[:body] assert_equal '1234', params['pId'] assert_equal 'a13d', params['authtoken'] - assert_equal payload.to_json, params['payload'] + assert_equal payload, JSON.parse(params['payload']) [200, {}, ''] end - - svc = service :push, data , payload + + svc = service :push, data, payload svc.receive end diff --git a/vendor/cache/activemodel-3.0.10.gem b/vendor/cache/activemodel-3.0.10.gem deleted file mode 100644 index 05014fd2d..000000000 Binary files a/vendor/cache/activemodel-3.0.10.gem and /dev/null differ diff --git a/vendor/cache/activemodel-4.2.10.gem b/vendor/cache/activemodel-4.2.10.gem new file mode 100644 index 000000000..4c3ba99f5 Binary files /dev/null and b/vendor/cache/activemodel-4.2.10.gem differ diff --git a/vendor/cache/activeresource-3.0.10.gem b/vendor/cache/activeresource-3.0.10.gem deleted file mode 100644 index 76e0e5ca1..000000000 Binary files a/vendor/cache/activeresource-3.0.10.gem and /dev/null differ diff --git a/vendor/cache/activeresource-4.0.0.gem b/vendor/cache/activeresource-4.0.0.gem new file mode 100644 index 000000000..cae16efd6 Binary files /dev/null and b/vendor/cache/activeresource-4.0.0.gem differ diff --git a/vendor/cache/activesupport-3.0.10.gem b/vendor/cache/activesupport-3.0.10.gem deleted file mode 100644 index 170354e29..000000000 Binary files a/vendor/cache/activesupport-3.0.10.gem and /dev/null differ diff --git a/vendor/cache/activesupport-4.2.10.gem b/vendor/cache/activesupport-4.2.10.gem new file mode 100644 index 000000000..addf8b408 Binary files /dev/null and b/vendor/cache/activesupport-4.2.10.gem differ diff --git a/vendor/cache/addressable-2.2.6.gem b/vendor/cache/addressable-2.2.6.gem deleted file mode 100644 index 14282d1df..000000000 Binary files a/vendor/cache/addressable-2.2.6.gem and /dev/null differ diff --git a/vendor/cache/addressable-2.5.2.gem b/vendor/cache/addressable-2.5.2.gem new file mode 100644 index 000000000..3e53ea0e4 Binary files /dev/null and b/vendor/cache/addressable-2.5.2.gem differ diff --git a/vendor/cache/amqp-0.6.7.gem b/vendor/cache/amqp-0.6.7.gem deleted file mode 100644 index 95f5d6c87..000000000 Binary files a/vendor/cache/amqp-0.6.7.gem and /dev/null differ diff --git a/vendor/cache/aws-sdk-1.67.0.gem b/vendor/cache/aws-sdk-1.67.0.gem new file mode 100644 index 000000000..9302695e6 Binary files /dev/null and b/vendor/cache/aws-sdk-1.67.0.gem differ diff --git a/vendor/cache/aws-sdk-core-2.0.48.gem b/vendor/cache/aws-sdk-core-2.0.48.gem new file mode 100644 index 000000000..41bc6e4a1 Binary files /dev/null and b/vendor/cache/aws-sdk-core-2.0.48.gem differ diff --git a/vendor/cache/aws-sdk-v1-1.67.0.gem b/vendor/cache/aws-sdk-v1-1.67.0.gem new file mode 100644 index 000000000..21548704c Binary files /dev/null and b/vendor/cache/aws-sdk-v1-1.67.0.gem differ diff --git a/vendor/cache/builder-2.1.2.gem b/vendor/cache/builder-2.1.2.gem deleted file mode 100644 index c90169723..000000000 Binary files a/vendor/cache/builder-2.1.2.gem and /dev/null differ diff --git a/vendor/cache/builder-3.2.3.gem b/vendor/cache/builder-3.2.3.gem new file mode 100644 index 000000000..3500c8043 Binary files /dev/null and b/vendor/cache/builder-3.2.3.gem differ diff --git a/vendor/cache/concurrent-ruby-1.0.5.gem b/vendor/cache/concurrent-ruby-1.0.5.gem new file mode 100644 index 000000000..4119d3aaf Binary files /dev/null and b/vendor/cache/concurrent-ruby-1.0.5.gem differ diff --git a/vendor/cache/crack-0.1.8.gem b/vendor/cache/crack-0.1.8.gem deleted file mode 100644 index b4c999222..000000000 Binary files a/vendor/cache/crack-0.1.8.gem and /dev/null differ diff --git a/vendor/cache/daemons-1.1.0.gem b/vendor/cache/daemons-1.1.0.gem deleted file mode 100644 index 363fcb41e..000000000 Binary files a/vendor/cache/daemons-1.1.0.gem and /dev/null differ diff --git a/vendor/cache/domain_name-0.5.20170404.gem b/vendor/cache/domain_name-0.5.20170404.gem new file mode 100644 index 000000000..e7235dd10 Binary files /dev/null and b/vendor/cache/domain_name-0.5.20170404.gem differ diff --git a/vendor/cache/eventmachine-0.12.10.gem b/vendor/cache/eventmachine-0.12.10.gem deleted file mode 100644 index aa54c34a3..000000000 Binary files a/vendor/cache/eventmachine-0.12.10.gem and /dev/null differ diff --git a/vendor/cache/eventmachine-1.2.5.gem b/vendor/cache/eventmachine-1.2.5.gem new file mode 100644 index 000000000..26c18a8e0 Binary files /dev/null and b/vendor/cache/eventmachine-1.2.5.gem differ diff --git a/vendor/cache/faraday-0.7.5.pre.gem b/vendor/cache/faraday-0.7.5.pre.gem deleted file mode 100644 index 5050c74ea..000000000 Binary files a/vendor/cache/faraday-0.7.5.pre.gem and /dev/null differ diff --git a/vendor/cache/faraday-0.9.0.gem b/vendor/cache/faraday-0.9.0.gem new file mode 100644 index 000000000..0b00e52ff Binary files /dev/null and b/vendor/cache/faraday-0.9.0.gem differ diff --git a/vendor/cache/faraday_middleware-0.12.2.gem b/vendor/cache/faraday_middleware-0.12.2.gem new file mode 100644 index 000000000..24c9f2567 Binary files /dev/null and b/vendor/cache/faraday_middleware-0.12.2.gem differ diff --git a/vendor/cache/faraday_middleware-0.7.0.gem b/vendor/cache/faraday_middleware-0.7.0.gem deleted file mode 100644 index 9af504ed8..000000000 Binary files a/vendor/cache/faraday_middleware-0.7.0.gem and /dev/null differ diff --git a/vendor/cache/hashie-1.1.0.gem b/vendor/cache/hashie-1.1.0.gem deleted file mode 100644 index 70a16c4db..000000000 Binary files a/vendor/cache/hashie-1.1.0.gem and /dev/null differ diff --git a/vendor/cache/hashie-2.1.2.gem b/vendor/cache/hashie-2.1.2.gem new file mode 100644 index 000000000..39f47dcbd Binary files /dev/null and b/vendor/cache/hashie-2.1.2.gem differ diff --git a/vendor/cache/http-cookie-1.0.3.gem b/vendor/cache/http-cookie-1.0.3.gem new file mode 100644 index 000000000..f02ecaf77 Binary files /dev/null and b/vendor/cache/http-cookie-1.0.3.gem differ diff --git a/vendor/cache/httparty-0.7.4.gem b/vendor/cache/httparty-0.7.4.gem deleted file mode 100644 index b47577632..000000000 Binary files a/vendor/cache/httparty-0.7.4.gem and /dev/null differ diff --git a/vendor/cache/i18n-0.5.0.gem b/vendor/cache/i18n-0.5.0.gem deleted file mode 100644 index 14b157dbc..000000000 Binary files a/vendor/cache/i18n-0.5.0.gem and /dev/null differ diff --git a/vendor/cache/i18n-0.9.1.gem b/vendor/cache/i18n-0.9.1.gem new file mode 100644 index 000000000..c8cb0dc54 Binary files /dev/null and b/vendor/cache/i18n-0.9.1.gem differ diff --git a/vendor/cache/jmespath-1.3.1.gem b/vendor/cache/jmespath-1.3.1.gem new file mode 100644 index 000000000..deba3208e Binary files /dev/null and b/vendor/cache/jmespath-1.3.1.gem differ diff --git a/vendor/cache/json-1.6.1.gem b/vendor/cache/json-1.6.1.gem deleted file mode 100644 index 257fbbba7..000000000 Binary files a/vendor/cache/json-1.6.1.gem and /dev/null differ diff --git a/vendor/cache/json-1.8.6.gem b/vendor/cache/json-1.8.6.gem new file mode 100644 index 000000000..2a7d6648e Binary files /dev/null and b/vendor/cache/json-1.8.6.gem differ diff --git a/vendor/cache/jwt-0.1.3.gem b/vendor/cache/jwt-0.1.3.gem deleted file mode 100644 index abc8867b9..000000000 Binary files a/vendor/cache/jwt-0.1.3.gem and /dev/null differ diff --git a/vendor/cache/jwt-2.1.0.gem b/vendor/cache/jwt-2.1.0.gem new file mode 100644 index 000000000..ebfe13720 Binary files /dev/null and b/vendor/cache/jwt-2.1.0.gem differ diff --git a/vendor/cache/mail-2.3.0.gem b/vendor/cache/mail-2.3.0.gem deleted file mode 100644 index f1dcdbe21..000000000 Binary files a/vendor/cache/mail-2.3.0.gem and /dev/null differ diff --git a/vendor/cache/mail-2.7.0.gem b/vendor/cache/mail-2.7.0.gem new file mode 100644 index 000000000..8db385adf Binary files /dev/null and b/vendor/cache/mail-2.7.0.gem differ diff --git a/vendor/cache/maxcdn-0.3.2.gem b/vendor/cache/maxcdn-0.3.2.gem new file mode 100644 index 000000000..30a3696ca Binary files /dev/null and b/vendor/cache/maxcdn-0.3.2.gem differ diff --git a/vendor/cache/mime-types-1.16.gem b/vendor/cache/mime-types-1.16.gem deleted file mode 100644 index 49f1ef203..000000000 Binary files a/vendor/cache/mime-types-1.16.gem and /dev/null differ diff --git a/vendor/cache/mime-types-1.25.1.gem b/vendor/cache/mime-types-1.25.1.gem new file mode 100644 index 000000000..877d8a97f Binary files /dev/null and b/vendor/cache/mime-types-1.25.1.gem differ diff --git a/vendor/cache/mini_mime-1.0.0.gem b/vendor/cache/mini_mime-1.0.0.gem new file mode 100644 index 000000000..cd814c59c Binary files /dev/null and b/vendor/cache/mini_mime-1.0.0.gem differ diff --git a/vendor/cache/mini_portile2-2.3.0.gem b/vendor/cache/mini_portile2-2.3.0.gem new file mode 100644 index 000000000..341a956b2 Binary files /dev/null and b/vendor/cache/mini_portile2-2.3.0.gem differ diff --git a/vendor/cache/minitest-5.10.3.gem b/vendor/cache/minitest-5.10.3.gem new file mode 100644 index 000000000..082361c26 Binary files /dev/null and b/vendor/cache/minitest-5.10.3.gem differ diff --git a/vendor/cache/mqtt-0.0.8.gem b/vendor/cache/mqtt-0.0.8.gem new file mode 100644 index 000000000..3297c913f Binary files /dev/null and b/vendor/cache/mqtt-0.0.8.gem differ diff --git a/vendor/cache/multi_json-1.0.3.gem b/vendor/cache/multi_json-1.0.3.gem deleted file mode 100644 index 5c85494dc..000000000 Binary files a/vendor/cache/multi_json-1.0.3.gem and /dev/null differ diff --git a/vendor/cache/multi_json-1.12.2.gem b/vendor/cache/multi_json-1.12.2.gem new file mode 100644 index 000000000..af3c0b462 Binary files /dev/null and b/vendor/cache/multi_json-1.12.2.gem differ diff --git a/vendor/cache/multipart-post-1.1.3.gem b/vendor/cache/multipart-post-1.1.3.gem deleted file mode 100644 index 752f1c1fe..000000000 Binary files a/vendor/cache/multipart-post-1.1.3.gem and /dev/null differ diff --git a/vendor/cache/multipart-post-2.0.0.gem b/vendor/cache/multipart-post-2.0.0.gem new file mode 100644 index 000000000..abfff3d20 Binary files /dev/null and b/vendor/cache/multipart-post-2.0.0.gem differ diff --git a/vendor/cache/net-http-persistent-2.9.4.gem b/vendor/cache/net-http-persistent-2.9.4.gem new file mode 100644 index 000000000..73f16a79f Binary files /dev/null and b/vendor/cache/net-http-persistent-2.9.4.gem differ diff --git a/vendor/cache/netrc-0.11.0.gem b/vendor/cache/netrc-0.11.0.gem new file mode 100644 index 000000000..78226f365 Binary files /dev/null and b/vendor/cache/netrc-0.11.0.gem differ diff --git a/vendor/cache/nokogiri-1.8.1.gem b/vendor/cache/nokogiri-1.8.1.gem new file mode 100644 index 000000000..970507ca4 Binary files /dev/null and b/vendor/cache/nokogiri-1.8.1.gem differ diff --git a/vendor/cache/polyglot-0.3.3.gem b/vendor/cache/polyglot-0.3.3.gem deleted file mode 100644 index 0b87664b8..000000000 Binary files a/vendor/cache/polyglot-0.3.3.gem and /dev/null differ diff --git a/vendor/cache/public_suffix-3.0.1.gem b/vendor/cache/public_suffix-3.0.1.gem new file mode 100644 index 000000000..e8fc0478e Binary files /dev/null and b/vendor/cache/public_suffix-3.0.1.gem differ diff --git a/vendor/cache/rack-1.3.0.gem b/vendor/cache/rack-1.3.0.gem deleted file mode 100644 index 7b1272bf4..000000000 Binary files a/vendor/cache/rack-1.3.0.gem and /dev/null differ diff --git a/vendor/cache/rails-observers-0.1.5.gem b/vendor/cache/rails-observers-0.1.5.gem new file mode 100644 index 000000000..c673b989a Binary files /dev/null and b/vendor/cache/rails-observers-0.1.5.gem differ diff --git a/vendor/cache/rake-0.8.7.gem b/vendor/cache/rake-0.8.7.gem deleted file mode 100644 index 0740cec7b..000000000 Binary files a/vendor/cache/rake-0.8.7.gem and /dev/null differ diff --git a/vendor/cache/rake-10.0.3.gem b/vendor/cache/rake-10.0.3.gem new file mode 100644 index 000000000..f645fa9ff Binary files /dev/null and b/vendor/cache/rake-10.0.3.gem differ diff --git a/vendor/cache/rest-client-2.0.2.gem b/vendor/cache/rest-client-2.0.2.gem new file mode 100644 index 000000000..3369c0a08 Binary files /dev/null and b/vendor/cache/rest-client-2.0.2.gem differ diff --git a/vendor/cache/signet-0.8.1.gem b/vendor/cache/signet-0.8.1.gem new file mode 100644 index 000000000..b2b22f3c4 Binary files /dev/null and b/vendor/cache/signet-0.8.1.gem differ diff --git a/vendor/cache/simple_oauth-0.1.5.gem b/vendor/cache/simple_oauth-0.1.5.gem deleted file mode 100644 index 7f5fe18d1..000000000 Binary files a/vendor/cache/simple_oauth-0.1.5.gem and /dev/null differ diff --git a/vendor/cache/simple_oauth-0.1.9.gem b/vendor/cache/simple_oauth-0.1.9.gem new file mode 100644 index 000000000..350d10055 Binary files /dev/null and b/vendor/cache/simple_oauth-0.1.9.gem differ diff --git a/vendor/cache/sinatra-1.2.6.gem b/vendor/cache/sinatra-1.2.6.gem deleted file mode 100644 index 64e231f21..000000000 Binary files a/vendor/cache/sinatra-1.2.6.gem and /dev/null differ diff --git a/vendor/cache/softlayer_messaging-1.0.2.gem b/vendor/cache/softlayer_messaging-1.0.2.gem new file mode 100644 index 000000000..9591d2e84 Binary files /dev/null and b/vendor/cache/softlayer_messaging-1.0.2.gem differ diff --git a/vendor/cache/statsd-ruby-0.3.0.github.1.gem b/vendor/cache/statsd-ruby-0.3.0.github.1.gem deleted file mode 100644 index aa5082c6e..000000000 Binary files a/vendor/cache/statsd-ruby-0.3.0.github.1.gem and /dev/null differ diff --git a/vendor/cache/thin-1.2.2.gem b/vendor/cache/thin-1.2.2.gem deleted file mode 100644 index 2df09c352..000000000 Binary files a/vendor/cache/thin-1.2.2.gem and /dev/null differ diff --git a/vendor/cache/thread_safe-0.3.6.gem b/vendor/cache/thread_safe-0.3.6.gem new file mode 100644 index 000000000..7ee950f8b Binary files /dev/null and b/vendor/cache/thread_safe-0.3.6.gem differ diff --git a/vendor/cache/tilt-1.2.2.gem b/vendor/cache/tilt-1.2.2.gem deleted file mode 100644 index a931fb8c3..000000000 Binary files a/vendor/cache/tilt-1.2.2.gem and /dev/null differ diff --git a/vendor/cache/tinder-1.10.0.gem b/vendor/cache/tinder-1.10.0.gem new file mode 100644 index 000000000..97415f5c9 Binary files /dev/null and b/vendor/cache/tinder-1.10.0.gem differ diff --git a/vendor/cache/tinder-1.7.0.gem b/vendor/cache/tinder-1.7.0.gem deleted file mode 100644 index 1c6c7d61c..000000000 Binary files a/vendor/cache/tinder-1.7.0.gem and /dev/null differ diff --git a/vendor/cache/treetop-1.4.10.gem b/vendor/cache/treetop-1.4.10.gem deleted file mode 100644 index 3c98f3d50..000000000 Binary files a/vendor/cache/treetop-1.4.10.gem and /dev/null differ diff --git a/vendor/cache/twilio-ruby-3.4.2.gem b/vendor/cache/twilio-ruby-3.4.2.gem deleted file mode 100644 index 0839ca3b7..000000000 Binary files a/vendor/cache/twilio-ruby-3.4.2.gem and /dev/null differ diff --git a/vendor/cache/twilio-ruby-3.9.0.gem b/vendor/cache/twilio-ruby-3.9.0.gem new file mode 100644 index 000000000..509d753c8 Binary files /dev/null and b/vendor/cache/twilio-ruby-3.9.0.gem differ diff --git a/vendor/cache/twitter-stream-0.1.14.gem b/vendor/cache/twitter-stream-0.1.14.gem deleted file mode 100644 index 01a63793b..000000000 Binary files a/vendor/cache/twitter-stream-0.1.14.gem and /dev/null differ diff --git a/vendor/cache/twitter-stream-0.1.16.gem b/vendor/cache/twitter-stream-0.1.16.gem new file mode 100644 index 000000000..aa7e340ee Binary files /dev/null and b/vendor/cache/twitter-stream-0.1.16.gem differ diff --git a/vendor/cache/tzinfo-1.2.4.gem b/vendor/cache/tzinfo-1.2.4.gem new file mode 100644 index 000000000..40fc7d747 Binary files /dev/null and b/vendor/cache/tzinfo-1.2.4.gem differ diff --git a/vendor/cache/unf-0.1.4.gem b/vendor/cache/unf-0.1.4.gem new file mode 100644 index 000000000..01f1852db Binary files /dev/null and b/vendor/cache/unf-0.1.4.gem differ diff --git a/vendor/cache/unf_ext-0.0.7.4.gem b/vendor/cache/unf_ext-0.0.7.4.gem new file mode 100644 index 000000000..444be9f6a Binary files /dev/null and b/vendor/cache/unf_ext-0.0.7.4.gem differ diff --git a/vendor/cache/xmlrpc-0.2.1.gem b/vendor/cache/xmlrpc-0.2.1.gem new file mode 100644 index 000000000..fd2d27f47 Binary files /dev/null and b/vendor/cache/xmlrpc-0.2.1.gem differ diff --git a/vendor/cache/xmpp4r-0.5.6.gem b/vendor/cache/xmpp4r-0.5.6.gem new file mode 100644 index 000000000..2226bc9c6 Binary files /dev/null and b/vendor/cache/xmpp4r-0.5.6.gem differ diff --git a/vendor/cache/xmpp4r-0.5.gem b/vendor/cache/xmpp4r-0.5.gem deleted file mode 100644 index a9c3d0f46..000000000 Binary files a/vendor/cache/xmpp4r-0.5.gem and /dev/null differ diff --git a/vendor/cache/xmpp4r-simple-0.8.8.gem b/vendor/cache/xmpp4r-simple-0.8.8.gem deleted file mode 100644 index c21c4f4da..000000000 Binary files a/vendor/cache/xmpp4r-simple-0.8.8.gem and /dev/null differ diff --git a/vendor/cache/yajl-ruby-1.1.0.gem b/vendor/cache/yajl-ruby-1.1.0.gem deleted file mode 100644 index 3fcb580e8..000000000 Binary files a/vendor/cache/yajl-ruby-1.1.0.gem and /dev/null differ diff --git a/vendor/cache/yajl-ruby-1.3.1.gem b/vendor/cache/yajl-ruby-1.3.1.gem new file mode 100644 index 000000000..8ac269cc0 Binary files /dev/null and b/vendor/cache/yajl-ruby-1.3.1.gem differ diff --git a/vendor/internal-gems/basecamp/lib/basecamp.rb b/vendor/internal-gems/basecamp/lib/basecamp.rb index a562b954c..58d587f96 100644 --- a/vendor/internal-gems/basecamp/lib/basecamp.rb +++ b/vendor/internal-gems/basecamp/lib/basecamp.rb @@ -170,7 +170,7 @@ def parent_resources(*parents) end def element_name - name.split(/::/).last.underscore + @element_name || name.split(/::/).last.underscore end def prefix_source @@ -233,8 +233,8 @@ def self.attachment_categories(project_id, options = {}) end class Message < Resource - parent_resources :project - set_element_name 'post' + self.prefix = "/projects/:project_id/" + self.element_name = "post" # Returns the most recent 25 messages in the given project (and category, # if specified). If you need to retrieve older messages, use the archive @@ -454,6 +454,7 @@ def establish_connection!(site, user, password, use_ssl = false) Resource.user = user Resource.password = password Resource.site = (use_ssl ? "https" : "http") + "://" + site + Resource.format = :xml @connection = Connection.new(self) end diff --git a/vendor/internal-gems/rubyforge/lib/rubyforge.rb b/vendor/internal-gems/rubyforge/lib/rubyforge.rb deleted file mode 100644 index 61b0e9714..000000000 --- a/vendor/internal-gems/rubyforge/lib/rubyforge.rb +++ /dev/null @@ -1,74 +0,0 @@ -# This code was pretty much copied from Ara Howard's -# RubyForge gem... thanks Ara! :) - -require 'net/https' -require 'openssl' -require 'webrick/cookie' - -class RubyForge - def initialize(username, password) - @cookies = Array.new - login(username, password) - end - - def post_news(group_id, subject, body) - url = URI.parse('http://rubyforge.org/news/submit.php') - form = { - 'group_id' => group_id.to_s, - 'post_changes' => 'y', - 'summary' => subject, - 'details' => body, - 'submit' => 'Submit' - } - execute(url, form) - end - - ####### - private - ####### - - def login(username, password) - url = URI.parse('https://rubyforge.org/account/login.php') - form = { - 'return_to' => '', - 'form_loginname' => username, - 'form_pw' => password, - 'login' => 'Login' - } - response = execute(url, form) - bake_cookies(url, response) - end - - def execute(url, parameters) - request = Net::HTTP::Post.new(url.request_uri) - request['Content-Type'] = 'application/x-www-form-urlencoded' - @cookies.each do |cookie| - request['Cookie'] = cookie - end - http = Net::HTTP.new(url.host, url.port) - if url.scheme == 'https' - http.use_ssl = true - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - end - request_data = query_string_for(parameters) - request['Content-Length'] = request_data.length.to_s - http.request(request, request_data) - end - - def bake_cookies(url, response) - (response.get_fields('Set-Cookie') || []).each do |raw_cookie| - WEBrick::Cookie.parse_set_cookies(raw_cookie).each do |baked_cookie| - baked_cookie.domain ||= url.host - baked_cookie.path ||= url.path - @cookies << baked_cookie - end - end - end - - def query_string_for(parameters) - parameters.sort_by { |k,v| k.to_s }.map { |k,v| - k && [ WEBrick::HTTPUtils.escape_form(k.to_s), - WEBrick::HTTPUtils.escape_form(v.to_s) ].join('=') - }.compact.join('&') - end -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/README b/vendor/internal-gems/yammer4r-0.1.5/README deleted file mode 100644 index 40290f9bc..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/README +++ /dev/null @@ -1,16 +0,0 @@ -= Yammer4R - -== Developers -* {Jim Patterson} - -== Description -Yammer4R provides an object based API to query or update your Yammer account via pure Ruby. It hides the ugly HTTP/REST code from your code. - -== External Dependencies -* Ruby 1.8 (tested with 1.8.7) -* JSON gem (tested with versions: 1.1.3) -* OAuth gem (tested with versions: 0.2.7) -* RSpec gem (tested with versions: 1.1.11) - -== Usage Examples -Coming soon... \ No newline at end of file diff --git a/vendor/internal-gems/yammer4r-0.1.5/Rakefile b/vendor/internal-gems/yammer4r-0.1.5/Rakefile deleted file mode 100644 index c183b6602..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/Rakefile +++ /dev/null @@ -1,13 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), 'lib')) - -require 'rubygems' -require 'rake' -require 'spec/rake/spectask' -require 'yammer4r' - -desc "Run all specs" -Spec::Rake::SpecTask.new('spec') do |t| - t.spec_files = FileList['spec/**/*spec.rb'] -end - -task :default => [:spec] \ No newline at end of file diff --git a/vendor/internal-gems/yammer4r-0.1.5/TODO b/vendor/internal-gems/yammer4r-0.1.5/TODO deleted file mode 100644 index 438a76403..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/TODO +++ /dev/null @@ -1,2 +0,0 @@ -Test! There are currently no tests for yammer4r, and that makes me very sad. -Switch to HTTParty instead of yammer_request. diff --git a/vendor/internal-gems/yammer4r-0.1.5/bin/yammer_create_oauth_yml.rb b/vendor/internal-gems/yammer4r-0.1.5/bin/yammer_create_oauth_yml.rb deleted file mode 100644 index 4c845e19f..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/bin/yammer_create_oauth_yml.rb +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env ruby - -# Instructions: -# -# Register your application at https://www.yammer.com/client_applications/new -# Upon successful registration, you'll recieve your consumer key and secret. -# Pass these values on the command line as --key (-k) and --secret (-s) then -# follow the instructions. - -require 'optparse' -require 'rubygems' -require 'oauth' - -OPTIONS = { - :outfile => 'oauth.yml' -} - -YAMMER_OAUTH = "https://www.yammer.com" - -ARGV.options do |o| - script_name = File.basename($0) - - o.set_summary_indent(' ') - o.banner = "Usage: #{script_name} [OPTIONS]" - o.define_head "Create a yaml file for yammer oauth" - o.separator "" - o.separator "[-k] and [-s] options are mandatory" - o.separator "" - - o.on("-o", "--outfile=[val]", String, - "Yaml output file", - "Default: #{OPTIONS[:outfile]}") { |OPTIONS[:outfile]| } - o.on("-k", "--key=val", String, - "Consumer key for Yammer app") { |key| OPTIONS[:key] = key} - o.on("-s", "--secret=val", String, - "Consumer secret for Yammer app") { |secret| OPTIONS[:secret] = secret} - - o.separator "" - - o.on_tail("-h", "--help", "Show this help message.") { puts o; exit } - o.parse! -end - -unless OPTIONS[:key] && OPTIONS[:secret] - raise ArgumentError, "Must supply consumer key and secret (use -h for help)" -end - -consumer = OAuth::Consumer.new OPTIONS[:key], OPTIONS[:secret], {:site => YAMMER_OAUTH} -request_token = consumer.get_request_token - -puts "Please visit the following URL in your browser to authorize your application, then enter the 4 character security code when done: #{request_token.authorize_url}" -oauth_verifier = gets -response = consumer.token_request(consumer.http_method, - (consumer.access_token_url? ? consumer.access_token_url : consumer.access_token_path), - request_token, - {}, - :oauth_verifier => oauth_verifier.chomp) -access_token = OAuth::AccessToken.new(consumer,response[:oauth_token],response[:oauth_token_secret]) - -oauth_yml = <<-EOT -consumer: - key: #{OPTIONS[:key]} - secret: #{OPTIONS[:secret]} -access: - token: #{access_token.token} - secret: #{access_token.secret} -EOT - -File.open(OPTIONS[:outfile], "w") do |f| - f.write oauth_yml -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/example.rb b/vendor/internal-gems/yammer4r-0.1.5/example.rb deleted file mode 100644 index 17e0eed42..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/example.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'yammer4r' - -config_path = File.dirname(__FILE__) + 'oauth.yml' -yammer = Yammer::Client.new(:config => config_path) - -# Get all messages -messages = yammer.messages -puts messages.size -puts messages.last.body.plain -puts messages.last.body.parsed - -# Print out all the users -yammer.users.each do |u| - puts "#{u.name} - #{u.me?}" -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/ext/core_ext.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/ext/core_ext.rb deleted file mode 100644 index fad18ac2d..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/ext/core_ext.rb +++ /dev/null @@ -1,30 +0,0 @@ -class String - def to_boolean - case self - when 'true' - true - when 'false' - false - else - nil - end - end -end - -class Hash - def symbolize_keys - inject({}) do |options, (key, value)| - options[(key.to_sym rescue key) || key] = value - options - end - end - - def symbolize_keys! - self.replace(self.symbolize_keys) - end - - def assert_has_keys(*valid_keys) - missing_keys = [valid_keys].flatten - keys - raise(ArgumentError, "Missing Option(s): #{missing_keys.join(", ")}") unless missing_keys.empty? - end -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/client.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/client.rb deleted file mode 100644 index 35a17a1cb..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/client.rb +++ /dev/null @@ -1,100 +0,0 @@ -module Yammer - class Client - def initialize(options={}) - options.assert_has_keys(:consumer, :access) unless options.has_key?(:config) - - yammer_url = options.delete(:yammer_host) || "https://www.yammer.com" - @api_path = "/api/v1/" - - if options[:config] - config = YAML.load(open(options[:config])) - options[:consumer] = config['consumer'].symbolize_keys - options[:access] = config['access'].symbolize_keys - end - - consumer = OAuth::Consumer.new(options[:consumer][:key], options[:consumer][:secret], :site => yammer_url) - consumer.http.set_debug_output($stderr) if options[:verbose] == true - @access_token = OAuth::AccessToken.new(consumer, options[:access][:token], options[:access][:secret]) - end - - - # TODO: modularize message and user handling - def messages(action = :all, params = {}) - params.merge!(:resource => :messages) - params.merge!(:action => action) unless action == :all - - parsed_response = JSON.parse(yammer_request(:get, params).body) - older_available = parsed_response['meta']['older_available'] - - ml = parsed_response['messages'].map do |m| - mash(m) - end - Yammer::MessageList.new(ml, older_available, self) - end - - # POST or DELETE a message - def message(action, params) - params.merge!(:resource => :messages) - yammer_request(action, params) - end - - def users(params = {}) - params.merge!(:resource => :users) - JSON.parse(yammer_request(:get, params).body).map { |u| Yammer::User.new(mash(u), self) } - end - - def user(id) - u = JSON.parse(yammer_request(:get, {:resource => :users, :id => id}).body) - Yammer::User.new(mash(u), self) - end - - def current_user - u = JSON.parse(yammer_request(:get, {:resource => :users, :action => :current}).body) - Yammer::User.new(mash(u), self) - end - alias_method :me, :current_user - - private - - def yammer_request(http_method, options) - request_uri = @api_path + options.delete(:resource).to_s - [:action, :id].each {|k| request_uri += "/#{options.delete(k)}" if options.has_key?(k) } - request_uri += ".json" - - if options.any? - request_uri += "?#{create_query_string(options)}" unless http_method == :post - end - - if http_method == :post - handle_response(@access_token.send(http_method, request_uri, options)) - else - handle_response(@access_token.send(http_method, request_uri)) - end - end - - def create_query_string(options) - options.map {|k, v| "#{OAuth::Helper.escape(k)}=#{OAuth::Helper.escape(v)}"}.join('&') - end - - def mash(json) - Mash.new(json) - end - - def handle_response(response) - # TODO: Write classes for exceptions - case response.code.to_i - when 200..201 - response - when 400 - raise "Bad Request: #{response.body}" - when 401 - raise "Authentication failed. Check your username and password" - when 503 - raise "503: Service Unavailable" - else - raise "Error. HTTP Response #{response.code}: #{response.body}" - end - end - - end -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message.rb deleted file mode 100644 index 3b097d83b..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Yammer::Message - - attr_reader :id, :url, :web_url, :replied_to_id, :thread_id, - :body_plain, :body_parsed, :message_type, :client_type, - :sender_id, :sender_type - - def initialize(m) - @id = m['id'] - @url = m['url'] - @web_url = m['web_url'] - @replied_to_id = m['replied_to_id'] - @thread_id = m['thread_id'] - @body_plain = m['body']['plain'] - @body_parsed = m['body']['parsed'] - @message_type = m['message_type'] - @client_type = m['client_type'] - @sender_id = m['sender_id'] - @sender_type = m['sender_type'] - begin - @created_at = m['created_at'] - rescue ArgumentError => e - @created_at = nil - end - end - -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message_list.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message_list.rb deleted file mode 100644 index 98928559e..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/message_list.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Yammer::MessageList < Array - - attr_reader :older_available, :ids - - def initialize(a, oa, c) - super(a) - @older_available = oa - @client = c - @ids = a.map {|m| m.id}.sort - end - - def first - self[0] - end - - def last - self[self.size - 1] - end - -end \ No newline at end of file diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/user.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/user.rb deleted file mode 100644 index ff965fe9c..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer/user.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Yammer::User - extend Forwardable - def_delegator :@user, :id - - def initialize(mash, client) - @user = mash - @client = client - end - - def me? - @user.id == @client.me.id - end - - def method_missing(call, *args) - @user.send(call, *args) - end -end diff --git a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer4r.rb b/vendor/internal-gems/yammer4r-0.1.5/lib/yammer4r.rb deleted file mode 100644 index 0907b43a7..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/lib/yammer4r.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'forwardable' -require 'rubygems' -require 'date' -require 'yaml' -require 'open-uri' - -#gem 'json', '>= 1.1.7' -#require 'json' - -#gem 'oauth', '>=0.3.5' -require 'oauth' - -#gem 'mash', '>=0.0.3' -require 'mash' - -$:.unshift(File.dirname(__FILE__)) -require 'ext/core_ext' -require 'yammer/client' -require 'yammer/message' -require 'yammer/message_list' -require 'yammer/user' diff --git a/vendor/internal-gems/yammer4r-0.1.5/oauth.yml.template b/vendor/internal-gems/yammer4r-0.1.5/oauth.yml.template deleted file mode 100644 index 248c13b47..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/oauth.yml.template +++ /dev/null @@ -1,7 +0,0 @@ -consumer: - key: CLIENT_KEY - secret: CLIENT_SECRET - -access: - token: CONSUMER_TOKEN - secret: CONSUMER_SECRET diff --git a/vendor/internal-gems/yammer4r-0.1.5/spec/spec_helper.rb b/vendor/internal-gems/yammer4r-0.1.5/spec/spec_helper.rb deleted file mode 100644 index 64f17e490..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/spec/spec_helper.rb +++ /dev/null @@ -1,3 +0,0 @@ -$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) -require 'yammer4r' -require 'spec' \ No newline at end of file diff --git a/vendor/internal-gems/yammer4r-0.1.5/spec/yammer/client_spec.rb b/vendor/internal-gems/yammer4r-0.1.5/spec/yammer/client_spec.rb deleted file mode 100644 index 2d06e049c..000000000 --- a/vendor/internal-gems/yammer4r-0.1.5/spec/yammer/client_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') -require 'ostruct' - -describe Yammer::Client do - - context "creating" do - - before(:each) do - mock_consumer = mock(OAuth::Consumer) - OAuth::Consumer.stub!("new").and_return(mock_consumer) - @mock_http = mock("http") - mock_consumer.stub!("http").and_return(@mock_http) - end - - it "can be configured to be verbose" do - @mock_http.should_receive("set_debug_output").with($stderr) - Yammer::Client.new(:consumer => {}, :access => {}, :verbose => true) - end - - it "should not be configured to be verbose unless asked to be" do - @mock_http.should_not_receive("set_debug_output") - Yammer::Client.new(:consumer => {}, :access => {}) - end - - it "should not be configured to be verbose if asked not to be" do - @mock_http.should_not_receive("set_debug_output") - Yammer::Client.new(:consumer => {}, :access => {}, :verbose => false) - end - - end - - context "users" do - - before(:each) do - @mock_access_token = mock(OAuth::AccessToken) - @response = OpenStruct.new(:code => 200, :body => '{}') - OAuth::AccessToken.stub!("new").and_return(@mock_access_token) - @client = Yammer::Client.new(:consumer => {}, :access => {}) - end - - it "should request the first page by default" do - @mock_access_token.should_receive("get").with("/api/v1/users.json").and_return(@response) - @client.users - end - - it "can request a specified page" do - @mock_access_token.should_receive("get").with("/api/v1/users.json?page=2").and_return(@response) - @client.users(:page => 2) - end - - end - -end \ No newline at end of file