Drone CI service

This commit is contained in:
Kirilll Zaitsev 2015-08-27 02:58:49 +03:00
parent 308c6428ae
commit 263abda3fd
15 changed files with 1037 additions and 131 deletions

View File

@ -31,6 +31,9 @@ v 8.0.0 (unreleased)
- Don't notify users without access to the project when they are (accidentally) mentioned in a note.
- Retrieving oauth token with LDAP credentials
- Load Application settings from running database unless env var USE_DB=false
- Added Drone CI integration (Kirill Zaitsev)
- Refactored service API and added automatically service docs generator (Kirill Zaitsev)
- Added web_url key project hook_attrs (Kirill Zaitsev)
v 7.14.1
- Improve abuse reports management from admin area

View File

@ -2,7 +2,7 @@ class Projects::ServicesController < Projects::ApplicationController
ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :build_type,
:build_key, :server, :teamcity_url, :drone_url, :build_type,
:description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,

View File

@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
has_many :services
has_one :gitlab_ci_service, dependent: :destroy
has_one :campfire_service, dependent: :destroy
has_one :drone_ci_service, dependent: :destroy
has_one :emails_on_push_service, dependent: :destroy
has_one :irker_service, dependent: :destroy
has_one :pivotaltracker_service, dependent: :destroy
@ -613,6 +614,7 @@ class Project < ActiveRecord::Base
name: name,
ssh_url: ssh_url_to_repo,
http_url: http_url_to_repo,
web_url: web_url,
namespace: namespace.name,
visibility_level: visibility_level
}

View File

@ -25,12 +25,24 @@ class CiService < Service
def category
:ci
end
def valid_token?(token)
self.respond_to?(:token) && self.token.present? && self.token == token
end
def supported_events
%w(push)
end
# Return complete url to build page
def merge_request_page(iid, sha, ref)
commit_page(sha, ref)
end
def commit_page(sha, ref)
build_page(sha, ref)
end
# Return complete url to merge_request page
#
# Ex.
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
@ -45,10 +57,27 @@ class CiService < Service
#
#
# Ex.
# @service.commit_status('13be4ac')
# @service.merge_request_status(9, '13be4ac', 'dev')
# # => 'success'
#
# @service.commit_status('2abe4ac')
# @service.merge_request_status(10, '2abe4ac', 'dev)
# # => 'running'
#
#
def merge_request_status(iid, sha, ref)
commit_status(sha, ref)
end
# Return string with build status or :error symbol
#
# Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
#
#
# Ex.
# @service.commit_status('13be4ac', 'master')
# # => 'success'
#
# @service.commit_status('2abe4ac', 'dev')
# # => 'running'
#
#

View File

@ -0,0 +1,170 @@
class DroneCiService < CiService
prop_accessor :drone_url, :token, :enable_ssl_verification
validates :drone_url,
presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
validates :token,
presence: true,
format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated?
def compose_service_hook
hook = service_hook || build_service_hook
hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join
hook.enable_ssl_verification = enable_ssl_verification
hook.save
end
def execute(data)
case data[:object_kind]
when 'push'
service_hook.execute(data) if push_valid?(data)
when 'merge_request'
service_hook.execute(data) if merge_request_valid?(data)
when 'tag_push'
service_hook.execute(data) if tag_push_valid?(data)
end
end
def allow_target_ci?
true
end
def supported_events
%w(push merge_request tag_push)
end
def merge_request_status_path(iid, sha = nil, ref = nil)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
"?access_token=#{token}"]
URI.join(*url).to_s
end
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
URI.join(*url).to_s
end
def merge_request_status(iid, sha, ref)
response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification)
if response.code == 200 and response['status']
case response['status']
when 'killed'
:canceled
when 'failure', 'error'
# Because drone return error if some test env failed
:failed
else
response["status"]
end
else
:error
end
rescue Errno::ECONNREFUSED
:error
end
def commit_status(sha, ref)
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
if response.code == 200 and response['status']
case response['status']
when 'killed'
:canceled
when 'failure', 'error'
# Because drone return error if some test env failed
:failed
else
response["status"]
end
else
:error
end
rescue Errno::ECONNREFUSED
:error
end
def merge_request_page(iid, sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
URI.join(*url).to_s
end
def commit_page(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
URI.join(*url).to_s
end
def commit_coverage(sha, ref)
nil
end
def build_page(sha, ref)
commit_page(sha, ref)
end
def builds_path
url = [drone_url, "#{project.namespace.path}/#{project.path}"]
URI.join(*url).to_s
end
def status_img_path
url = [drone_url,
"api/badges/#{project.namespace.path}/#{project.path}/status.svg",
"?branch=#{URI::encode(project.default_branch)}"]
URI.join(*url).to_s
end
def title
'Drone CI'
end
def description
'Drone is a Continuous Integration platform built on Docker, written in Go'
end
def to_param
'drone_ci'
end
def fields
[
{ type: 'text', name: 'token', placeholder: 'Drone CI project specific token' },
{ type: 'text', name: 'drone_url', placeholder: 'http://drone.example.com' },
{ type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" }
]
end
private
def tag_push_valid?(data)
data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after])
end
def push_valid?(data)
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
source_branch: Gitlab::Git.ref_name(data[:ref]))
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
!Gitlab::Git.blank_ref?(data[:after])
end
def merge_request_valid?(data)
['opened', 'reopened'].include?(data[:object_attributes][:state]) &&
data[:object_attributes][:merge_status] == 'unchecked'
end
end

View File

@ -135,6 +135,7 @@ class Service < ActiveRecord::Base
buildkite
campfire
custom_issue_tracker
drone_ci
emails_on_push
external_wiki
flowdock

View File

@ -1,8 +1,296 @@
# Services
## Asana
Asana - Teamwork without email
### Create/Edit Asana service
Set Asana service for a project.
> 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/123456/987654`) or task IDs starting with # (for example, `#987654`). Every task ID found will get the commit comment added to it. You can also close a task with a message containing: `fix #123456`. You can find your Api Keys here: http://developer.asana.com/documentation/#api_keys
```
PUT /projects/:id/services/asana
```
Parameters:
- `api_key` (**required**) - User API token. User must have access to task,all comments will be attributed to this user.
- `restrict_to_branch` (optional) - Comma-separated list of branches which will beautomatically inspected. Leave blank to include all branches.
### Delete Asana service
Delete Asana service for a project.
```
DELETE /projects/:id/services/asana
```
## Assembla
Project Management Software (Source Commits Endpoint)
### Create/Edit Assembla service
Set Assembla service for a project.
```
PUT /projects/:id/services/assembla
```
Parameters:
- `token` (**required**)
- `subdomain` (optional)
### Delete Assembla service
Delete Assembla service for a project.
```
DELETE /projects/:id/services/assembla
```
## Atlassian Bamboo CI
A continuous integration and build server
### Create/Edit Atlassian Bamboo CI service
Set Atlassian Bamboo CI service for a project.
> You must set up automatic revision labeling and a repository trigger in Bamboo.
```
PUT /projects/:id/services/bamboo
```
Parameters:
- `bamboo_url` (**required**) - Bamboo root URL like https://bamboo.example.com
- `build_key` (**required**) - Bamboo build plan key like KEY
- `username` (**required**) - A user with API access, if applicable
- `password` (**required**)
### Delete Atlassian Bamboo CI service
Delete Atlassian Bamboo CI service for a project.
```
DELETE /projects/:id/services/bamboo
```
## Buildkite
Continuous integration and deployments
### Create/Edit Buildkite service
Set Buildkite service for a project.
```
PUT /projects/:id/services/buildkite
```
Parameters:
- `token` (**required**) - Buildkite project GitLab token
- `project_url` (**required**) - https://buildkite.com/example/project
- `enable_ssl_verification` (optional) - Enable SSL verification
### Delete Buildkite service
Delete Buildkite service for a project.
```
DELETE /projects/:id/services/buildkite
```
## Campfire
Simple web-based real-time group chat
### Create/Edit Campfire service
Set Campfire service for a project.
```
PUT /projects/:id/services/campfire
```
Parameters:
- `token` (**required**)
- `subdomain` (optional)
- `room` (optional)
### Delete Campfire service
Delete Campfire service for a project.
```
DELETE /projects/:id/services/campfire
```
## Custom Issue Tracker
Custom issue tracker
### Create/Edit Custom Issue Tracker service
Set Custom Issue Tracker service for a project.
```
PUT /projects/:id/services/custom-issue-tracker
```
Parameters:
- `new_issue_url` (**required**) - New Issue url
- `issues_url` (**required**) - Issue url
- `project_url` (**required**) - Project url
- `description` (optional) - Custom issue tracker
- `title` (optional) - Custom Issue Tracker
### Delete Custom Issue Tracker service
Delete Custom Issue Tracker service for a project.
```
DELETE /projects/:id/services/custom-issue-tracker
```
## Drone CI
Drone is a Continuous Integration platform built on Docker, written in Go
### Create/Edit Drone CI service
Set Drone CI service for a project.
```
PUT /projects/:id/services/drone-ci
```
Parameters:
- `token` (**required**) - Drone CI project specific token
- `drone_url` (**required**) - http://drone.example.com
- `enable_ssl_verification` (optional) - Enable SSL verification
### Delete Drone CI service
Delete Drone CI service for a project.
```
DELETE /projects/:id/services/drone-ci
```
## Emails on push
Email the commits and diff of each push to a list of recipients.
### Create/Edit Emails on push service
Set Emails on push service for a project.
```
PUT /projects/:id/services/emails-on-push
```
Parameters:
- `recipients` (**required**) - Emails separated by whitespace
- `disable_diffs` (optional) - Disable code diffs
- `send_from_committer_email` (optional) - Send from committer
### Delete Emails on push service
Delete Emails on push service for a project.
```
DELETE /projects/:id/services/emails-on-push
```
## External Wiki
Replaces the link to the internal wiki with a link to an external wiki.
### Create/Edit External Wiki service
Set External Wiki service for a project.
```
PUT /projects/:id/services/external-wiki
```
Parameters:
- `external_wiki_url` (**required**) - The URL of the external Wiki
### Delete External Wiki service
Delete External Wiki service for a project.
```
DELETE /projects/:id/services/external-wiki
```
## Flowdock
Flowdock is a collaboration web app for technical teams.
### Create/Edit Flowdock service
Set Flowdock service for a project.
```
PUT /projects/:id/services/flowdock
```
Parameters:
- `token` (**required**) - Flowdock Git source token
### Delete Flowdock service
Delete Flowdock service for a project.
```
DELETE /projects/:id/services/flowdock
```
## Gemnasium
Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.
### Create/Edit Gemnasium service
Set Gemnasium service for a project.
```
PUT /projects/:id/services/gemnasium
```
Parameters:
- `api_key` (**required**) - Your personal API KEY on gemnasium.com
- `token` (**required**) - The project's slug on gemnasium.com
### Delete Gemnasium service
Delete Gemnasium service for a project.
```
DELETE /projects/:id/services/gemnasium
```
## GitLab CI
### Edit GitLab CI service
Continuous integration server from GitLab
### Create/Edit GitLab CI service
Set GitLab CI service for a project.
@ -12,12 +300,13 @@ PUT /projects/:id/services/gitlab-ci
Parameters:
- `token` (required) - CI project token
- `project_url` (required) - CI project URL
- `token` (**required**) - GitLab CI project specific token
- `project_url` (**required**) - http://ci.gitlabhq.com/projects/3
- `enable_ssl_verification` (optional) - Enable SSL verification
### Delete GitLab CI service
Delete GitLab CI service settings for a project.
Delete GitLab CI service for a project.
```
DELETE /projects/:id/services/gitlab-ci
@ -25,17 +314,24 @@ DELETE /projects/:id/services/gitlab-ci
## HipChat
### Edit HipChat service
Private group chat and IM
Set HipChat service for project.
### Create/Edit HipChat service
Set HipChat service for a project.
```
PUT /projects/:id/services/hipchat
```
Parameters:
- `token` (required) - HipChat token
- `room` (required) - HipChat room name
- `token` (**required**) - Room token
- `color` (optional)
- `notify` (optional)
- `room` (optional) - Room name or ID
- `api_version` (optional) - Leave blank for default (v2)
- `server` (optional) - Leave blank for default. https://hipchat.example.com
### Delete HipChat service
@ -44,3 +340,197 @@ Delete HipChat service for a project.
```
DELETE /projects/:id/services/hipchat
```
## Irker (IRC gateway)
Send IRC messages, on update, to a list of recipients through an Irker gateway.
### Create/Edit Irker (IRC gateway) service
Set Irker (IRC gateway) service for a project.
> NOTE: Irker does NOT have built-in authentication, which makes it vulnerable to spamming IRC channels if it is hosted outside of a firewall. Please make sure you run the daemon within a secured network to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.
```
PUT /projects/:id/services/irker
```
Parameters:
- `recipients` (**required**) - Recipients/channels separated by whitespaces
- `default_irc_uri` (optional) - irc://irc.network.net:6697/
- `server_port` (optional) - 6659
- `server_host` (optional) - localhost
- `colorize_messages` (optional)
### Delete Irker (IRC gateway) service
Delete Irker (IRC gateway) service for a project.
```
DELETE /projects/:id/services/irker
```
## JIRA
Jira issue tracker
### Create/Edit JIRA service
Set JIRA service for a project.
> Setting `project_url`, `issues_url` and `new_issue_url` will allow a user to easily navigate to the Jira issue tracker. See the [integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) for details. Support for referencing commits and automatic closing of Jira issues directly from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)
```
PUT /projects/:id/services/jira
```
Parameters:
- `new_issue_url` (**required**) - New Issue url
- `project_url` (**required**) - Project url
- `issues_url` (**required**) - Issue url
- `description` (optional) - Jira issue tracker
### Delete JIRA service
Delete JIRA service for a project.
```
DELETE /projects/:id/services/jira
```
## PivotalTracker
Project Management Software (Source Commits Endpoint)
### Create/Edit PivotalTracker service
Set PivotalTracker service for a project.
```
PUT /projects/:id/services/pivotaltracker
```
Parameters:
- `token` (**required**)
### Delete PivotalTracker service
Delete PivotalTracker service for a project.
```
DELETE /projects/:id/services/pivotaltracker
```
## Pushover
Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.
### Create/Edit Pushover service
Set Pushover service for a project.
```
PUT /projects/:id/services/pushover
```
Parameters:
- `api_key` (**required**) - Your application key
- `user_key` (**required**) - Your user key
- `priority` (**required**)
- `device` (optional) - Leave blank for all active devices
- `sound` (optional)
### Delete Pushover service
Delete Pushover service for a project.
```
DELETE /projects/:id/services/pushover
```
## Redmine
Redmine issue tracker
### Create/Edit Redmine service
Set Redmine service for a project.
```
PUT /projects/:id/services/redmine
```
Parameters:
- `new_issue_url` (**required**) - New Issue url
- `project_url` (**required**) - Project url
- `issues_url` (**required**) - Issue url
- `description` (optional) - Redmine issue tracker
### Delete Redmine service
Delete Redmine service for a project.
```
DELETE /projects/:id/services/redmine
```
## Slack
A team communication tool for the 21st century
### Create/Edit Slack service
Set Slack service for a project.
```
PUT /projects/:id/services/slack
```
Parameters:
- `webhook` (**required**) - https://hooks.slack.com/services/...
- `username` (optional) - username
- `channel` (optional) - #channel
### Delete Slack service
Delete Slack service for a project.
```
DELETE /projects/:id/services/slack
```
## JetBrains TeamCity CI
A continuous integration and build server
### Create/Edit JetBrains TeamCity CI service
Set JetBrains TeamCity CI service for a project.
> The build configuration in Teamcity must use the build format number %build.vcs.number% you will also want to configure monitoring of all branches so merge requests build, that setting is in the vsc root advanced settings.
```
PUT /projects/:id/services/teamcity
```
Parameters:
- `teamcity_url` (**required**) - TeamCity root URL like https://teamcity.example.com
- `build_type` (**required**) - Build configuration ID
- `username` (**required**) - A user with permissions to trigger a manual build
- `password` (**required**)
### Delete JetBrains TeamCity CI service
Delete JetBrains TeamCity CI service for a project.
```
DELETE /projects/:id/services/teamcity
```

View File

@ -279,6 +279,7 @@ X-Gitlab-Event: Note Hook
"name": "Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
"http_url": "http://example.com/gitlab-org/gitlab-test.git",
"web_url": "http://example.com/gitlab-org/gitlab-test",
"namespace": "Gitlab Org",
"visibility_level": 10
},
@ -286,6 +287,7 @@ X-Gitlab-Event: Note Hook
"name": "Gitlab Test",
"ssh_url": "git@example.com:gitlab-org/gitlab-test.git",
"http_url": "http://example.com/gitlab-org/gitlab-test.git",
"web_url": "http://example.com/gitlab-org/gitlab-test",
"namespace": "Gitlab Org",
"visibility_level": 10
},
@ -462,6 +464,7 @@ X-Gitlab-Event: Merge Request Hook
"name": "awesome_project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
"http_url": "http://example.com/awesome_space/awesome_project.git",
"web_url": "http://example.com/awesome_space/awesome_project",
"visibility_level": 20,
"namespace": "awesome_space"
},
@ -469,6 +472,7 @@ X-Gitlab-Event: Merge Request Hook
"name": "awesome_project",
"ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git",
"http_url": "http://example.com/awesome_space/awesome_project.git",
"web_url": "http://example.com/awesome_space/awesome_project",
"visibility_level": 20,
"namespace": "awesome_space"
},

View File

@ -55,6 +55,32 @@ module API
end
end
def project_service
@project_service ||= begin
underscored_service = params[:service_slug].underscore
if Service.available_services_names.include?(underscored_service)
user_project.build_missing_services
service_method = "#{underscored_service}_service"
send_service(service_method)
end
end
@project_service || not_found!("Service")
end
def send_service(service_method)
user_project.send(service_method)
end
def service_attributes
@service_attributes ||= project_service.fields.inject([]) do |arr, hash|
arr << hash[:name].to_sym
end
end
def find_group(id)
begin
group = Group.find(id)

View File

@ -4,73 +4,49 @@ module API
before { authenticate! }
before { authorize_admin_project }
resource :projects do
# Set GitLab CI service for project
#
# Parameters:
# token (required) - CI project token
# project_url (required) - CI project url
# Set <service_slug> service for project
#
# Example Request:
#
# PUT /projects/:id/services/gitlab-ci
put ":id/services/gitlab-ci" do
required_attributes! [:token, :project_url]
attrs = attributes_for_keys [:token, :project_url]
user_project.build_missing_services
#
put ':id/services/:service_slug' do
if project_service
validators = project_service.class.validators.select do |s|
s.class == ActiveRecord::Validations::PresenceValidator &&
s.attributes != [:project_id]
end
if user_project.gitlab_ci_service.update_attributes(attrs.merge(active: true))
true
else
not_found!
required_attributes! validators.map(&:attributes).flatten.uniq
attrs = attributes_for_keys service_attributes
if project_service.update_attributes(attrs.merge(active: true))
true
else
not_found!
end
end
end
# Delete GitLab CI service settings
# Delete <service_slug> service for project
#
# Example Request:
# DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
active: false,
token: nil,
project_url: nil
)
end
end
# Set Hipchat service for project
#
# Parameters:
# token (required) - Hipchat token
# room (required) - Hipchat room name
# DELETE /project/:id/services/gitlab-ci
#
# Example Request:
# PUT /projects/:id/services/hipchat
put ':id/services/hipchat' do
required_attributes! [:token, :room]
attrs = attributes_for_keys [:token, :room]
user_project.build_missing_services
if user_project.hipchat_service.update_attributes(
attrs.merge(active: true))
true
else
not_found!
end
end
# Delete Hipchat service settings
#
# Example Request:
# DELETE /projects/:id/services/hipchat
delete ':id/services/hipchat' do
if user_project.hipchat_service
user_project.hipchat_service.update_attributes(
active: false,
token: nil,
room: nil
)
delete ':id/services/:service_slug' do
if project_service
attrs = service_attributes.inject({}) do |hash, key|
hash.merge!(key => nil)
end
if project_service.update_attributes(attrs.merge(active: false))
true
else
not_found!
end
end
end
end

View File

@ -10,7 +10,7 @@ module Grack
@request = Rack::Request.new(env)
@auth = Request.new(env)
@gitlab_ci = false
@ci = false
# Need this patch due to the rails mount
# Need this if under RELATIVE_URL_ROOT
@ -28,7 +28,7 @@ module Grack
if project && authorized_request?
# Tell gitlab-git-http-server the request is OK, and what the GL_ID is
render_grack_auth_ok
elsif @user.nil? && !@gitlab_ci
elsif @user.nil? && !@ci
unauthorized
else
render_not_found
@ -47,8 +47,8 @@ module Grack
# Allow authentication for GitLab CI service
# if valid token passed
if gitlab_ci_request?(login, password)
@gitlab_ci = true
if ci_request?(login, password)
@ci = true
return
end
@ -60,12 +60,17 @@ module Grack
end
end
def gitlab_ci_request?(login, password)
if login == "gitlab-ci-token" && project && project.gitlab_ci?
token = project.gitlab_ci_service.token
def ci_request?(login, password)
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
if token.present? && token == password && git_cmd == 'git-upload-pack'
return true
if project && matched_login.present? && git_cmd == 'git-upload-pack'
underscored_service = matched_login['s'].underscore
if Service.available_services_names.include?(underscored_service)
service_method = "#{underscored_service}_service"
service = project.send(service_method)
return service && service.activated? && service.valid_token?(password)
end
end
@ -124,7 +129,7 @@ module Grack
end
def authorized_request?
return true if @gitlab_ci
return true if @ci
case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS

89
lib/tasks/services.rake Normal file
View File

@ -0,0 +1,89 @@
services_template = <<-ERB
# Services
<% services.each do |service| %>
## <%= service[:title] %>
<% unless service[:description].blank? %>
<%= service[:description] %>
<% end %>
### Create/Edit <%= service[:title] %> service
Set <%= service[:title] %> service for a project.
<% unless service[:help].blank? %>
> <%= service[:help].gsub("\n", ' ') %>
<% end %>
```
PUT /projects/:id/services/<%= service[:dashed_name] %>
```
Parameters:
<% service[:params].each do |param| %>
- `<%= param[:name] %>` <%= param[:required] ? "(**required**)" : "(optional)" %><%= [" -", param[:description]].join(" ").gsub("\n", '') unless param[:description].blank? %>
<% end %>
### Delete <%= service[:title] %> service
Delete <%= service[:title] %> service for a project.
```
DELETE /projects/:id/services/<%= service[:dashed_name] %>
```
<% end %>
ERB
namespace :services do
task :doc do
services = Service.available_services_names.map do |s|
service_start = Time.now
klass = "#{s}_service".classify.constantize
service = klass.new
service_hash = {}
service_hash[:title] = service.title
service_hash[:dashed_name] = s.dasherize
service_hash[:description] = service.description
service_hash[:help] = service.help
service_hash[:params] = service.fields.map do |p|
param_hash = {}
param_hash[:name] = p[:name]
param_hash[:description] = p[:placeholder] || p[:title]
param_hash[:required] = klass.validators_on(p[:name].to_sym).any? do |v|
v.class == ActiveRecord::Validations::PresenceValidator
end
param_hash
end.sort_by { |p| p[:required] ? 0 : 1 }
puts "Collected data for: #{service.title}, #{Time.now-service_start}"
service_hash
end
doc_start = Time.now
doc_path = File.join(Rails.root, 'doc', 'api', 'services.md')
result = ERB.new(services_template, 0 , '>')
.result(OpenStruct.new(services: services).instance_eval { binding })
File.open(doc_path, 'w') do |f|
f.write result
end
puts "write a new service.md to: #{doc_path.to_s}, #{Time.now-doc_start}"
end
end

View File

@ -0,0 +1,107 @@
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
# template :boolean default(FALSE)
# push_events :boolean default(TRUE)
# issues_events :boolean default(TRUE)
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
#
require 'spec_helper'
describe DroneCiService do
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) }
end
describe 'validations' do
context 'active' do
before { allow(subject).to receive(:activated?).and_return(true) }
it { is_expected.to validate_presence_of(:token) }
it { is_expected.to validate_presence_of(:drone_url) }
it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
it { is_expected.not_to allow_value('token with spaces').for(:token) }
it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
end
context 'inactive' do
before { allow(subject).to receive(:activated?).and_return(false) }
it { is_expected.not_to validate_presence_of(:token) }
it { is_expected.not_to validate_presence_of(:drone_url) }
it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
it { is_expected.to allow_value('token with spaces').for(:token) }
it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
end
end
shared_context :drone_ci_service do
let(:drone) { DroneCiService.new }
let(:project) { create(:project, name: 'project') }
let(:path) { "#{project.namespace.path}/#{project.path}" }
let(:drone_url) { 'http://drone.example.com' }
let(:sha) { '2ab7834c' }
let(:branch) { 'dev' }
let(:token) { 'secret' }
let(:iid) { rand(1..9999) }
before(:each) do
allow(drone).to receive_messages(
project_id: project.id,
project: project,
active: true,
drone_url: drone_url,
token: token
)
end
end
describe "service page/path methods" do
include_context :drone_ci_service
# URL's
let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path) }
end
describe "execute" do
include_context :drone_ci_service
let(:user) { create(:user, username: 'username') }
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
it do
service_hook = double
expect(service_hook).to receive(:execute)
expect(drone).to receive(:service_hook).and_return(service_hook)
drone.execute(push_sample_data)
end
end
end

View File

@ -5,64 +5,47 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
describe "POST /projects/:id/services/gitlab-ci" do
it "should update gitlab-ci settings" do
put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
Service.available_services_names.each do |service|
describe "PUT /projects/:id/services/#{service.dasherize}" do
include_context service
expect(response.status).to eq(200)
it "should update #{service} settings" do
put api("/projects/#{project.id}/services/#{dashed_service}", user), service_attrs
expect(response.status).to eq(200)
end
it "should return if required fields missing" do
attrs = service_attrs
required_attributes = service_attrs_list.select do |attr|
service_klass.validators_on(attr).any? do |v|
v.class == ActiveRecord::Validations::PresenceValidator
end
end
if required_attributes.empty?
expected_code = 200
else
attrs.delete(required_attributes.shuffle.first)
expected_code = 400
end
put api("/projects/#{project.id}/services/#{dashed_service}", user), attrs
expect(response.status).to eq(expected_code)
end
end
it "should return if required fields missing" do
put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true
describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
expect(response.status).to eq(400)
end
it "should delete #{service}" do
delete api("/projects/#{project.id}/services/#{dashed_service}", user)
it "should return if the format of token is invalid" do
put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
expect(response.status).to eq(404)
end
it "should return if the format of token is invalid" do
put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
expect(response.status).to eq(404)
end
end
describe "DELETE /projects/:id/services/gitlab-ci" do
it "should update gitlab-ci settings" do
delete api("/projects/#{project.id}/services/gitlab-ci", user)
expect(response.status).to eq(200)
expect(project.gitlab_ci_service).to be_nil
end
end
describe 'PUT /projects/:id/services/hipchat' do
it 'should update hipchat settings' do
put api("/projects/#{project.id}/services/hipchat", user),
token: 'secret-token', room: 'test'
expect(response.status).to eq(200)
expect(project.hipchat_service).not_to be_nil
end
it 'should return if required fields missing' do
put api("/projects/#{project.id}/services/gitlab-ci", user),
token: 'secret-token', active: true
expect(response.status).to eq(400)
end
end
describe 'DELETE /projects/:id/services/hipchat' do
it 'should delete hipchat settings' do
delete api("/projects/#{project.id}/services/hipchat", user)
expect(response.status).to eq(200)
expect(project.hipchat_service).to be_nil
expect(response.status).to eq(200)
expect(project.send(service_method).activated?).to be_falsey
end
end
end
end

View File

@ -0,0 +1,21 @@
Service.available_services_names.each do |service|
shared_context service do
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
hash.merge!(k => 'secrettoken')
elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com")
elsif service == 'irker' && k == :recipients
hash.merge!(k => 'irc://irc.network.net:666/#channel')
else
hash.merge!(k => "someword")
end
end
end
end
end