Commit 927a7556 authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'grapify-service-api' into 'master'

Grapify the service API

Related to #22928 

See merge request !7970
parents 61844de4 8d0645c2
...@@ -3,7 +3,8 @@ require "addressable/uri" ...@@ -3,7 +3,8 @@ require "addressable/uri"
class BuildkiteService < CiService class BuildkiteService < CiService
ENDPOINT = "https://buildkite.com" ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token, :enable_ssl_verification prop_accessor :project_url, :token
boolean_accessor :enable_ssl_verification
validates :project_url, presence: true, url: true, if: :activated? validates :project_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
......
class DroneCiService < CiService class DroneCiService < CiService
prop_accessor :drone_url, :token, :enable_ssl_verification prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification
validates :drone_url, presence: true, url: true, if: :activated? validates :drone_url, presence: true, url: true, if: :activated?
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
......
class EmailsOnPushService < Service class EmailsOnPushService < Service
prop_accessor :send_from_committer_email boolean_accessor :send_from_committer_email
prop_accessor :disable_diffs boolean_accessor :disable_diffs
prop_accessor :recipients prop_accessor :recipients
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :activated?
...@@ -33,11 +33,11 @@ class EmailsOnPushService < Service ...@@ -33,11 +33,11 @@ class EmailsOnPushService < Service
end end
def send_from_committer_email? def send_from_committer_email?
self.send_from_committer_email == "1" Gitlab::Utils.to_boolean(self.send_from_committer_email)
end end
def disable_diffs? def disable_diffs?
self.disable_diffs == "1" Gitlab::Utils.to_boolean(self.disable_diffs)
end end
def fields def fields
......
...@@ -8,8 +8,8 @@ class HipchatService < Service ...@@ -8,8 +8,8 @@ class HipchatService < Service
ul ol li dl dt dd ul ol li dl dt dd
] ]
prop_accessor :token, :room, :server, :notify, :color, :api_version prop_accessor :token, :room, :server, :color, :api_version
boolean_accessor :notify_only_broken_builds boolean_accessor :notify_only_broken_builds, :notify
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def initialize_properties def initialize_properties
...@@ -75,7 +75,7 @@ class HipchatService < Service ...@@ -75,7 +75,7 @@ class HipchatService < Service
end end
def message_options(data = nil) def message_options(data = nil)
{ notify: notify.present? && notify == '1', color: message_color(data) } { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) }
end end
def create_message(data) def create_message(data)
......
...@@ -2,7 +2,8 @@ require 'uri' ...@@ -2,7 +2,8 @@ require 'uri'
class IrkerService < Service class IrkerService < Service
prop_accessor :server_host, :server_port, :default_irc_uri prop_accessor :server_host, :server_port, :default_irc_uri
prop_accessor :colorize_messages, :recipients, :channels prop_accessor :recipients, :channels
boolean_accessor :colorize_messages
validates :recipients, presence: true, if: :activated? validates :recipients, presence: true, if: :activated?
before_validation :get_channels before_validation :get_channels
......
...@@ -139,6 +139,43 @@ Get Buildkite service settings for a project. ...@@ -139,6 +139,43 @@ Get Buildkite service settings for a project.
GET /projects/:id/services/buildkite GET /projects/:id/services/buildkite
``` ```
## Build-Emails
Get emails for GitLab CI builds.
### Create/Edit Build-Emails service
Set Build-Emails service for a project.
```
PUT /projects/:id/services/builds-email
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
| `notify_only_broken_builds` | boolean | no | Notify only broken builds |
### Delete Build-Emails service
Delete Build-Emails service for a project.
```
DELETE /projects/:id/services/builds-email
```
### Get Build-Emails service settings
Get Build-Emails service settings for a project.
```
GET /projects/:id/services/builds-email
```
## Campfire ## Campfire
Simple web-based real-time group chat Simple web-based real-time group chat
...@@ -476,12 +513,11 @@ PUT /projects/:id/services/jira ...@@ -476,12 +513,11 @@ PUT /projects/:id/services/jira
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `active` | boolean| no | Enable/disable the JIRA service. |
| `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. | | `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. |
| `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. | | `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. |
| `username` | string | no | The username of the user created to be used with GitLab/JIRA. | | `username` | string | no | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | no | The password of the user created to be used with GitLab/JIRA. | | `password` | string | no | The password of the user created to be used with GitLab/JIRA. |
| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | | `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
### Delete JIRA service ### Delete JIRA service
...@@ -491,6 +527,78 @@ Remove all previously JIRA settings from a project. ...@@ -491,6 +527,78 @@ Remove all previously JIRA settings from a project.
DELETE /projects/:id/services/jira DELETE /projects/:id/services/jira
``` ```
## Mattermost Slash Commands
Ability to receive slash commands from a Mattermost chat instance.
### Create/Edit Mattermost Slash Command service
Set Mattermost Slash Command for a project.
```
PUT /projects/:id/services/mattermost-slash-commands
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `token` | string | yes | The Mattermost token |
### Delete Mattermost Slash Command service
Delete Mattermost Slash Command service for a project.
```
DELETE /projects/:id/services/mattermost-slash-commands
```
### Get Mattermost Slash Command service settings
Get Mattermost Slash Command service settings for a project.
```
GET /projects/:id/services/mattermost-slash-commands
```
## Pipeline-Emails
Get emails for GitLab CI pipelines.
### Create/Edit Pipeline-Emails service
Set Pipeline-Emails service for a project.
```
PUT /projects/:id/services/pipelines-email
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
| `notify_only_broken_builds` | boolean | no | Notify only broken pipelines |
### Delete Pipeline-Emails service
Delete Pipeline-Emails service for a project.
```
DELETE /projects/:id/services/pipelines-email
```
### Get Pipeline-Emails service settings
Get Pipeline-Emails service settings for a project.
```
GET /projects/:id/services/pipelines-email
```
## PivotalTracker ## PivotalTracker
Project Management Software (Source Commits Endpoint) Project Management Software (Source Commits Endpoint)
......
...@@ -96,17 +96,6 @@ module API ...@@ -96,17 +96,6 @@ module API
end end
end end
def project_service(project = user_project)
@project_service ||= project.find_or_initialize_service(params[:service_slug].underscore)
@project_service || not_found!("Service")
end
def service_attributes
@service_attributes ||= project_service.fields.inject([]) do |arr, hash|
arr << hash[:name].to_sym
end
end
def find_group(id) def find_group(id)
if id =~ /^\d+$/ if id =~ /^\d+$/
Group.find_by(id: id) Group.find_by(id: id)
......
module API module API
# Projects API
class Services < Grape::API class Services < Grape::API
services = {
'asana' => [
{
required: true,
name: :api_key,
type: String,
desc: 'User API token'
},
{
required: false,
name: :restrict_to_branch,
type: String,
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
}
],
'assembla' => [
{
required: true,
name: :token,
type: String,
desc: 'The authentication token'
},
{
required: false,
name: :subdomain,
type: String,
desc: 'Subdomain setting'
}
],
'bamboo' => [
{
required: true,
name: :bamboo_url,
type: String,
desc: 'Bamboo root URL like https://bamboo.example.com'
},
{
required: true,
name: :build_key,
type: String,
desc: 'Bamboo build plan key like'
},
{
required: true,
name: :username,
type: String,
desc: 'A user with API access, if applicable'
},
{
required: true,
name: :password,
type: String,
desc: 'Passord of the user'
}
],
'bugzilla' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'New issue URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'Issues URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'Project URL'
},
{
required: false,
name: :description,
type: String,
desc: 'Description'
},
{
required: false,
name: :title,
type: String,
desc: 'Title'
}
],
'buildkite' => [
{
required: true,
name: :token,
type: String,
desc: 'Buildkite project GitLab token'
},
{
required: true,
name: :project_url,
type: String,
desc: 'The buildkite project URL'
},
{
required: false,
name: :enable_ssl_verification,
type: Boolean,
desc: 'Enable SSL verification for communication'
}
],
'builds-email' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :add_pusher,
type: Boolean,
desc: 'Add pusher to recipients list'
},
{
required: false,
name: :notify_only_broken_builds,
type: Boolean,
desc: 'Notify only broken builds'
}
],
'campfire' => [
{
required: true,
name: :token,
type: String,
desc: 'Campfire token'
},
{
required: false,
name: :subdomain,
type: String,
desc: 'Campfire subdomain'
},
{
required: false,
name: :room,
type: String,
desc: 'Campfire room'
},
],
'custom-issue-tracker' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'New issue URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'Issues URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'Project URL'
},
{
required: false,
name: :description,
type: String,
desc: 'Description'
},
{
required: false,
name: :title,
type: String,
desc: 'Title'
}
],
'drone-ci' => [
{
required: true,
name: :token,
type: String,
desc: 'Drone CI token'
},
{
required: true,
name: :drone_url,
type: String,
desc: 'Drone CI URL'
},
{
required: false,
name: :enable_ssl_verification,
type: Boolean,
desc: 'Enable SSL verification for communication'
}
],
'emails-on-push' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :disable_diffs,
type: Boolean,
desc: 'Disable code diffs'
},
{
required: false,
name: :send_from_committer_email,
type: Boolean,
desc: 'Send from committer'
}
],
'external-wiki' => [
{
required: true,
name: :external_wiki_url,
type: String,
desc: 'The URL of the external Wiki'
}
],
'flowdock' => [
{
required: true,
name: :token,
type: String,
desc: 'Flowdock token'
}
],
'gemnasium' => [
{
required: true,
name: :api_key,
type: String,
desc: 'Your personal API key on gemnasium.com'
},
{
required: true,
name: :token,
type: String,
desc: "The project's slug on gemnasium.com"
}
],
'hipchat' => [
{
required: true,
name: :token,
type: String,
desc: 'The room token'
},
{
required: false,
name: :room,
type: String,
desc: 'The room name or ID'
},
{
required: false,
name: :color,
type: String,
desc: 'The room color'
},
{
required: false,
name: :notify,
type: Boolean,
desc: 'Enable notifications'
},
{
required: false,
name: :api_version,
type: String,
desc: 'Leave blank for default (v2)'
},
{
required: false,
name: :server,
type: String,
desc: 'Leave blank for default. https://hipchat.example.com'
}
],
'irker' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Recipients/channels separated by whitespaces'
},
{
required: false,
name: :default_irc_uri,
type: String,
desc: 'Default: irc://irc.network.net:6697'
},
{
required: false,
name: :server_host,
type: String,
desc: 'Server host. Default localhost'
},
{
required: false,
name: :server_port,
type: Integer,
desc: 'Server port. Default 6659'
},
{
required: false,
name: :colorize_messages,
type: Boolean,
desc: 'Colorize messages'
}
],
'jira' => [
{
required: true,
name: :url,
type: String,
desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com'
},
{
required: true,
name: :project_key,
type: String,
desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ'
},
{
required: false,
name: :username,
type: String,
desc: 'The username of the user created to be used with GitLab/JIRA'
},
{
required: false,
name: :password,
type: String,
desc: 'The password of the user created to be used with GitLab/JIRA'
},
{
required: false,
name: :jira_issue_transition_id,
type: Integer,
desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
'mattermost-slash-commands' => [
{
required: true,
name: :token,
type: String,
desc: 'The Mattermost token'
}
],
'pipelines-email' => [
{
required: true,
name: :recipients,
type: String,
desc: 'Comma-separated list of recipient email addresses'
},
{
required: false,
name: :notify_only_broken_builds,
type: Boolean,
desc: 'Notify only broken builds'
}
],
'pivotaltracker' => [
{
required: true,
name: :token,
type: String,
desc: 'The Pivotaltracker token'
},
{
required: false,
name: :restrict_to_branch,
type: String,
desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
}
],
'pushover' => [
{
required: true,
name: :api_key,
type: String,
desc: 'The application key'
},
{
required: true,
name: :user_key,
type: String,
desc: 'The user key'
},
{
required: true,
name: :priority,
type: String,
desc: 'The priority'
},
{
required: true,
name: :device,
type: String,
desc: 'Leave blank for all active devices'
},
{
required: true,
name: :sound,
type: String,
desc: 'The sound of the notification'
}
],
'redmine' => [
{
required: true,
name: :new_issue_url,
type: String,
desc: 'The new issue URL'
},
{
required: true,
name: :project_url,
type: String,
desc: 'The project URL'
},
{
required: true,
name: :issues_url,
type: String,
desc: 'The issues URL'
},
{
required: false,
name: :description,
type: String,
desc: 'The description of the tracker'
}
],
'slack' => [
{
required: true,
name: :webhook,
type: String,
desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...'
},
{
required: false,
name: :new_issue_url,
type: String,
desc: 'The user name'
},
{
required: false,
name: :channel,
type: String,
desc: 'The channel name'
}
],
'teamcity' => [
{
required: true,
name: :teamcity_url,
type: String,
desc: 'TeamCity root URL like https://teamcity.example.com'
},
{
required: true,
name: :build_type,
type: String,
desc: 'Build configuration ID'
},
{
required: true,
name: :username,
type: String,
desc: 'A user with permissions to trigger a manual build'
},
{
required: true,
name: :password,
type: String,
desc: 'The password of the user'
}
]
}.freeze
trigger_services = {
'mattermost-slash-commands' => [
{
name: :token,
type: String,
desc: 'The Mattermost token'
}
]
}.freeze
resource :projects do resource :projects do
before { authenticate! } before { authenticate! }
before { authorize_admin_project } before { authorize_admin_project }
# Set <service_slug> service for project helpers do
# def service_attributes(service)
# Example Request: service.fields.inject([]) do |arr, hash|
# arr << hash[:name].to_sym
# PUT /projects/:id/services/gitlab-ci 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 end
required_attributes! validators.map(&:attributes).flatten.uniq services.each do |service_slug, settings|
attrs = attributes_for_keys service_attributes desc "Set #{service_slug} service for project"
params do
settings.each do |setting|
if setting[:required]
requires setting[:name], type: setting[:type], desc: setting[:desc]
else
optional setting[:name], type: setting[:type], desc: setting[:desc]
end
end
end
put ":id/services/#{service_slug}" do
service = user_project.find_or_initialize_service(service_slug.underscore)
service_params = declared_params(include_missing: false).merge(active: true)
if project_service.update_attributes(attrs.merge(active: true)) if service.update_attributes(service_params)
true true
else else
not_found! render_api_error!('400 Bad Request', 400)
end end
end end
end end
# Delete <service_slug> service for project desc "Delete a service for project"
# params do
# Example Request: requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
# end
# DELETE /project/:id/services/gitlab-ci delete ":id/services/:service_slug" do
# service = user_project.find_or_initialize_service(params[:service_slug].underscore)
delete ':id/services/:service_slug' do
if project_service attrs = service_attributes(service).inject({}) do |hash, key|
attrs = service_attributes.inject({}) do |hash, key|
hash.merge!(key => nil) hash.merge!(key => nil)
end end
if project_service.update_attributes(attrs.merge(active: false)) if service.update_attributes(attrs.merge(active: false))
true true
else else
not_found! render_api_error!('400 Bad Request', 400)
end
end end
end end
# Get <service_slug> service settings for project desc 'Get the service settings for project' do
# success Entities::ProjectService
# Example Request: end
# params do
# GET /project/:id/services/gitlab-ci requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
# end
get ':id/services/:service_slug' do get ":id/services/:service_slug" do
present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? service = user_project.find_or_initialize_service(params[:service_slug].underscore)
present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
end end
end end
trigger_services.each do |service_slug, settings|
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do resource :projects do
desc 'Trigger a slash command' do desc "Trigger a slash command for #{service_slug}" do
detail 'Added in GitLab 8.13' detail 'Added in GitLab 8.13'
end end
post ':id/services/:service_slug/trigger' do params do
settings.each do |setting|
requires setting[:name], type: setting[:type], desc: setting[:desc]
end
end
post ":id/services/#{service_slug.underscore}/trigger" do
project = find_project(params[:id]) project = find_project(params[:id])
# This is not accurate, but done to prevent leakage of the project names # This is not accurate, but done to prevent leakage of the project names
not_found!('Service') unless project not_found!('Service') unless project
service = project_service(project) service = project.find_or_initialize_service(service_slug.underscore)
result = service.try(:active?) && service.try(:trigger, params) result = service.try(:active?) && service.try(:trigger, params)
...@@ -83,4 +600,5 @@ module API ...@@ -83,4 +600,5 @@ module API
end end
end end
end end
end
end end
...@@ -2,6 +2,7 @@ require "spec_helper" ...@@ -2,6 +2,7 @@ require "spec_helper"
describe API::Services, api: true do describe API::Services, api: true do
include ApiHelpers include ApiHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
...@@ -98,7 +99,7 @@ describe API::Services, api: true do ...@@ -98,7 +99,7 @@ describe API::Services, api: true do
post api("/projects/#{project.id}/services/idonotexist/trigger") post api("/projects/#{project.id}/services/idonotexist/trigger")
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
expect(json_response["message"]).to eq("404 Service Not Found") expect(json_response["error"]).to eq("404 Not Found")
end end
end end
...@@ -114,7 +115,7 @@ describe API::Services, api: true do ...@@ -114,7 +115,7 @@ describe API::Services, api: true do
end end
it 'when the service is inactive' do it 'when the service is inactive' do
post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger") post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
......
...@@ -16,8 +16,14 @@ Service.available_services_names.each do |service| ...@@ -16,8 +16,14 @@ Service.available_services_names.each do |service|
hash.merge!(k => 'secrettoken') hash.merge!(k => 'secrettoken')
elsif k =~ /^(.*_url|url|webhook)/ elsif k =~ /^(.*_url|url|webhook)/
hash.merge!(k => "http://example.com") hash.merge!(k => "http://example.com")
elsif service_klass.method_defined?("#{k}?")
hash.merge!(k => true)
elsif service == 'irker' && k == :recipients elsif service == 'irker' && k == :recipients
hash.merge!(k => 'irc://irc.network.net:666/#channel') hash.merge!(k => 'irc://irc.network.net:666/#channel')
elsif service == 'irker' && k == :server_port
hash.merge!(k => 1234)
elsif service == 'jira' && k == :jira_issue_transition_id
hash.merge!(k => 1234)
else else
hash.merge!(k => "someword") hash.merge!(k => "someword")
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment