Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG3
-rw-r--r--app/controllers/projects/services_controller.rb2
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_services/ci_service.rb37
-rw-r--r--app/models/project_services/drone_ci_service.rb170
-rw-r--r--app/models/service.rb1
-rw-r--r--doc/api/services.md506
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--lib/api/helpers.rb26
-rw-r--r--lib/api/services.rb86
-rw-r--r--lib/gitlab/backend/grack_auth.rb25
-rw-r--r--lib/tasks/services.rake89
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb107
-rw-r--r--spec/requests/api/services_spec.rb99
-rw-r--r--spec/support/services_shared_context.rb21
15 files changed, 1042 insertions, 136 deletions
diff --git a/CHANGELOG b/CHANGELOG
index f2ac3b979a2..0c401a99d48 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b0cf5866d41..3a22ed832ac 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -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,
diff --git a/app/models/project.rb b/app/models/project.rb
index 69f9af91c51..8e33a4b2f0f 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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
}
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 803402c83ee..88186113c68 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -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.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')
+ # @service.commit_status('2abe4ac', 'dev')
# # => 'running'
#
#
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
new file mode 100644
index 00000000000..39f732c8113
--- /dev/null
+++ b/app/models/project_services/drone_ci_service.rb
@@ -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
diff --git a/app/models/service.rb b/app/models/service.rb
index dcef2866c3b..60fcc9d2857 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -135,6 +135,7 @@ class Service < ActiveRecord::Base
buildkite
campfire
custom_issue_tracker
+ drone_ci
emails_on_push
external_wiki
flowdock
diff --git a/doc/api/services.md b/doc/api/services.md
index cbf767d1b25..bc5049dd302 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -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
+
+### Create/Edit HipChat service
-Set HipChat service for project.
+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
+```
+
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 73717ffc7d6..09400d9b163 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -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"
},
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 1ebf9a1f022..76c9cc2e3a4 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -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)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 3ad59cf3adf..73645cedea4 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -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
-
- if user_project.gitlab_ci_service.update_attributes(attrs.merge(active: true))
- true
- else
- not_found!
- end
- end
-
- # Delete GitLab CI service settings
#
- # 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
+ 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
- # Set Hipchat service for project
- #
- # Parameters:
- # token (required) - Hipchat token
- # room (required) - Hipchat room name
- #
- # 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
+ required_attributes! validators.map(&:attributes).flatten.uniq
+ attrs = attributes_for_keys service_attributes
- if user_project.hipchat_service.update_attributes(
- attrs.merge(active: true))
- true
- else
- not_found!
+ if project_service.update_attributes(attrs.merge(active: true))
+ true
+ else
+ not_found!
+ end
end
end
- # Delete Hipchat service settings
+ # Delete <service_slug> service for project
#
# 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 /project/:id/services/gitlab-ci
+ #
+ 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
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index dc87aa52a3e..aa46c9a6d49 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -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
diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake
new file mode 100644
index 00000000000..53d912d2a7c
--- /dev/null
+++ b/lib/tasks/services.rake
@@ -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
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
new file mode 100644
index 00000000000..bad9a9e6e1a
--- /dev/null
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -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
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 6d29a28580a..c297904614a 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -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"
-
- expect(response.status).to eq(200)
- 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
-
- expect(response.status).to eq(400)
- 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: "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
+ Service.available_services_names.each do |service|
+ describe "PUT /projects/:id/services/#{service.dasherize}" do
+ include_context service
+
+ 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
+
+ describe "DELETE /projects/:id/services/#{service.dasherize}" do
+ include_context service
+
+ it "should delete #{service}" do
+ delete api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+ expect(response.status).to eq(200)
+ expect(project.send(service_method).activated?).to be_falsey
+ end
end
end
end
diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb
new file mode 100644
index 00000000000..4d007ae55ee
--- /dev/null
+++ b/spec/support/services_shared_context.rb
@@ -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