2018-03-27 19:54:05 +05:30
# frozen_string_literal: true
2014-09-02 18:07:02 +05:30
module API
class Services < Grape :: API
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_SETTINGS = [
2018-03-17 18:26:18 +05:30
{
required : true ,
name : :webhook ,
type : String ,
desc : 'The chat webhook'
} ,
{
required : false ,
name : :username ,
type : String ,
desc : 'The chat username'
} ,
{
required : false ,
name : :channel ,
type : String ,
desc : 'The default chat channel'
}
2018-03-27 19:54:05 +05:30
] . freeze
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_FLAGS = [
2018-03-17 18:26:18 +05:30
{
required : false ,
name : :notify_only_broken_pipelines ,
type : Boolean ,
desc : 'Send notifications for broken pipelines'
} ,
{
required : false ,
name : :notify_only_default_branch ,
type : Boolean ,
desc : 'Send notifications only for the default branch'
}
2018-03-27 19:54:05 +05:30
] . freeze
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_CHANNELS = [
2018-03-17 18:26:18 +05:30
{
required : false ,
name : :push_channel ,
type : String ,
desc : 'The name of the channel to receive push_events notifications'
} ,
{
required : false ,
name : :issue_channel ,
type : String ,
desc : 'The name of the channel to receive issues_events notifications'
} ,
{
required : false ,
name : :confidential_issue_channel ,
type : String ,
desc : 'The name of the channel to receive confidential_issues_events notifications'
} ,
{
required : false ,
name : :merge_request_channel ,
type : String ,
desc : 'The name of the channel to receive merge_requests_events notifications'
} ,
{
required : false ,
name : :note_channel ,
type : String ,
desc : 'The name of the channel to receive note_events notifications'
} ,
{
required : false ,
name : :tag_push_channel ,
type : String ,
desc : 'The name of the channel to receive tag_push_events notifications'
} ,
{
required : false ,
name : :pipeline_channel ,
type : String ,
desc : 'The name of the channel to receive pipeline_events notifications'
} ,
{
required : false ,
name : :wiki_page_channel ,
type : String ,
desc : 'The name of the channel to receive wiki_page_events notifications'
}
2018-03-27 19:54:05 +05:30
] . freeze
2018-03-17 18:26:18 +05:30
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_EVENTS = [
2018-03-17 18:26:18 +05:30
{
required : false ,
name : :push_events ,
type : Boolean ,
desc : 'Enable notifications for push_events'
} ,
{
required : false ,
name : :issues_events ,
type : Boolean ,
desc : 'Enable notifications for issues_events'
} ,
{
required : false ,
name : :confidential_issues_events ,
type : Boolean ,
desc : 'Enable notifications for confidential_issues_events'
} ,
{
required : false ,
name : :merge_requests_events ,
type : Boolean ,
desc : 'Enable notifications for merge_requests_events'
} ,
{
required : false ,
name : :note_events ,
type : Boolean ,
desc : 'Enable notifications for note_events'
} ,
{
required : false ,
name : :tag_push_events ,
type : Boolean ,
desc : 'Enable notifications for tag_push_events'
} ,
{
required : false ,
name : :pipeline_events ,
type : Boolean ,
desc : 'Enable notifications for pipeline_events'
} ,
{
required : false ,
name : :wiki_page_events ,
type : Boolean ,
desc : 'Enable notifications for wiki_page_events'
}
2018-03-27 19:54:05 +05:30
] . freeze
2018-03-17 18:26:18 +05:30
2017-08-17 22:00:37 +05:30
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'
}
] ,
'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'
}
] ,
2018-12-23 12:14:25 +05:30
'discord' = > [
{
required : true ,
name : :webhook ,
type : String ,
desc : 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
}
] ,
2017-08-17 22:00:37 +05:30
'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'
}
] ,
2018-11-18 11:00:15 +05:30
'hangouts-chat' = > [
{
required : true ,
name : :webhook ,
type : String ,
desc : 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
}
] ,
2017-08-17 22:00:37 +05:30
'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 ,
2017-09-10 17:25:29 +05:30
desc : 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
2017-08-17 22:00:37 +05:30
} ,
{
2017-09-10 17:25:29 +05:30
required : false ,
name : :api_url ,
2017-08-17 22:00:37 +05:30
type : String ,
2017-09-10 17:25:29 +05:30
desc : 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
2017-08-17 22:00:37 +05:30
} ,
{
2018-03-17 18:26:18 +05:30
required : true ,
2017-08-17 22:00:37 +05:30
name : :username ,
type : String ,
desc : 'The username of the user created to be used with GitLab/JIRA'
} ,
{
2018-03-17 18:26:18 +05:30
required : true ,
2017-08-17 22:00:37 +05:30
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`'
}
] ,
2015-09-25 12:07:36 +05:30
2017-08-17 22:00:37 +05:30
'kubernetes' = > [
{
required : true ,
name : :namespace ,
type : String ,
desc : 'The Kubernetes namespace to use'
} ,
{
required : true ,
name : :api_url ,
type : String ,
desc : 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
} ,
{
required : true ,
name : :token ,
type : String ,
desc : 'The service token to authenticate against the Kubernetes cluster with'
} ,
{
required : false ,
name : :ca_pem ,
type : String ,
desc : 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
2017-09-10 17:25:29 +05:30
}
2017-08-17 22:00:37 +05:30
] ,
'mattermost-slash-commands' = > [
{
required : true ,
name : :token ,
type : String ,
desc : 'The Mattermost token'
}
] ,
'slack-slash-commands' = > [
{
required : true ,
name : :token ,
type : String ,
desc : 'The Slack token'
}
] ,
2018-03-17 18:26:18 +05:30
'packagist' = > [
{
required : true ,
name : :username ,
type : String ,
desc : 'The username'
} ,
{
required : true ,
name : :token ,
type : String ,
desc : 'The Packagist API token'
} ,
{
required : false ,
name : :server ,
type : String ,
desc : 'The server'
}
] ,
2017-08-17 22:00:37 +05:30
'pipelines-email' = > [
{
required : true ,
name : :recipients ,
type : String ,
desc : 'Comma-separated list of recipient email addresses'
} ,
{
required : false ,
name : :notify_only_broken_pipelines ,
type : Boolean ,
desc : 'Notify only broken pipelines'
}
] ,
'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.'
}
] ,
'prometheus' = > [
{
required : true ,
name : :api_url ,
type : String ,
desc : 'Prometheus API Base URL, like http://prometheus.example.com/'
}
] ,
'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' = > [
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_SETTINGS ,
CHAT_NOTIFICATION_FLAGS ,
CHAT_NOTIFICATION_CHANNELS ,
CHAT_NOTIFICATION_EVENTS
2018-03-17 18:26:18 +05:30
] . flatten ,
2017-08-17 22:00:37 +05:30
'microsoft-teams' = > [
{
required : true ,
name : :webhook ,
type : String ,
desc : 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
}
] ,
'mattermost' = > [
2018-03-27 19:54:05 +05:30
CHAT_NOTIFICATION_SETTINGS ,
CHAT_NOTIFICATION_FLAGS ,
CHAT_NOTIFICATION_CHANNELS ,
CHAT_NOTIFICATION_EVENTS
2018-03-17 18:26:18 +05:30
] . flatten ,
2017-08-17 22:00:37 +05:30
'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'
}
]
}
2014-09-02 18:07:02 +05:30
2017-08-17 22:00:37 +05:30
service_classes = [
AsanaService ,
AssemblaService ,
BambooService ,
BugzillaService ,
BuildkiteService ,
CampfireService ,
CustomIssueTrackerService ,
2018-12-23 12:14:25 +05:30
DiscordService ,
2017-08-17 22:00:37 +05:30
DroneCiService ,
EmailsOnPushService ,
ExternalWikiService ,
FlowdockService ,
2018-11-18 11:00:15 +05:30
HangoutsChatService ,
2017-08-17 22:00:37 +05:30
HipchatService ,
IrkerService ,
JiraService ,
KubernetesService ,
MattermostSlashCommandsService ,
SlackSlashCommandsService ,
2018-03-17 18:26:18 +05:30
PackagistService ,
2017-08-17 22:00:37 +05:30
PipelinesEmailService ,
PivotaltrackerService ,
PrometheusService ,
PushoverService ,
RedmineService ,
SlackService ,
MattermostService ,
MicrosoftTeamsService ,
2017-09-10 17:25:29 +05:30
TeamcityService
2017-08-17 22:00:37 +05:30
]
if Rails . env . development?
services [ 'mock-ci' ] = [
{
required : true ,
name : :mock_service_url ,
type : String ,
desc : 'URL to the mock service'
}
]
services [ 'mock-deployment' ] = [ ]
services [ 'mock-monitoring' ] = [ ]
service_classes += [
MockCiService ,
MockDeploymentService ,
2017-09-10 17:25:29 +05:30
MockMonitoringService
2017-08-17 22:00:37 +05:30
]
end
2018-03-27 19:54:05 +05:30
SERVICES = services . freeze
SERVICE_CLASSES = service_classes . freeze
SERVICE_CLASSES . each do | service |
event_names = service . try ( :event_names ) || next
event_names . each do | event_name |
SERVICES [ service . to_param . tr ( " _ " , " - " ) ] << {
required : false ,
name : event_name . to_sym ,
type : String ,
2018-05-09 12:01:36 +05:30
desc : service . event_description ( event_name )
2018-03-27 19:54:05 +05:30
}
end
end
TRIGGER_SERVICES = {
2017-08-17 22:00:37 +05:30
'mattermost-slash-commands' = > [
{
name : :token ,
type : String ,
desc : 'The Mattermost token'
}
] ,
'slack-slash-commands' = > [
{
name : :token ,
type : String ,
desc : 'The Slack token'
}
]
} . freeze
params do
requires :id , type : String , desc : 'The ID of a project'
end
2018-12-23 12:14:25 +05:30
resource :projects , requirements : API :: NAMESPACE_OR_PROJECT_REQUIREMENTS do
2017-08-17 22:00:37 +05:30
before { authenticate! }
before { authorize_admin_project }
helpers do
def service_attributes ( service )
service . fields . inject ( [ ] ) do | arr , hash |
arr << hash [ :name ] . to_sym
2015-09-25 12:07:36 +05:30
end
2014-09-02 18:07:02 +05:30
end
end
2018-03-27 19:54:05 +05:30
SERVICES . each do | service_slug , settings |
2017-08-17 22:00:37 +05:30
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 )
2018-11-18 11:00:15 +05:30
if service . update ( service_params )
2018-03-17 18:26:18 +05:30
present service , with : Entities :: ProjectService
2015-09-25 12:07:36 +05:30
else
2017-08-17 22:00:37 +05:30
render_api_error! ( '400 Bad Request' , 400 )
end
end
end
desc " Delete a service for project "
params do
2018-03-27 19:54:05 +05:30
requires :service_slug , type : String , values : SERVICES . keys , desc : 'The name of the service'
2017-08-17 22:00:37 +05:30
end
delete " :id/services/:service_slug " do
service = user_project . find_or_initialize_service ( params [ :service_slug ] . underscore )
2018-03-17 18:26:18 +05:30
destroy_conditionally! ( service ) do
attrs = service_attributes ( service ) . inject ( { } ) do | hash , key |
hash . merge! ( key = > nil )
end
2017-08-17 22:00:37 +05:30
2018-11-18 11:00:15 +05:30
unless service . update ( attrs . merge ( active : false ) )
2018-03-17 18:26:18 +05:30
render_api_error! ( '400 Bad Request' , 400 )
end
2017-08-17 22:00:37 +05:30
end
end
desc 'Get the service settings for project' do
success Entities :: ProjectService
end
params do
2018-03-27 19:54:05 +05:30
requires :service_slug , type : String , values : SERVICES . keys , desc : 'The name of the service'
2017-08-17 22:00:37 +05:30
end
get " :id/services/:service_slug " do
service = user_project . find_or_initialize_service ( params [ :service_slug ] . underscore )
present service , with : Entities :: ProjectService , include_passwords : current_user . admin?
end
end
2018-03-27 19:54:05 +05:30
TRIGGER_SERVICES . each do | service_slug , settings |
2017-08-17 22:00:37 +05:30
helpers do
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2017-09-10 17:25:29 +05:30
def slash_command_service ( project , service_slug , params )
2017-08-17 22:00:37 +05:30
project . services . active . where ( template : false ) . find do | service |
service . try ( :token ) == params [ :token ] && service . to_param == service_slug . underscore
2015-09-25 12:07:36 +05:30
end
2015-04-26 12:48:37 +05:30
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2015-04-26 12:48:37 +05:30
end
2017-08-17 22:00:37 +05:30
params do
requires :id , type : String , desc : 'The ID of a project'
end
2018-12-23 12:14:25 +05:30
resource :projects , requirements : API :: NAMESPACE_OR_PROJECT_REQUIREMENTS do
2017-08-17 22:00:37 +05:30
desc " Trigger a slash command for #{ service_slug } " do
detail 'Added in GitLab 8.13'
end
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 ] )
# This is not accurate, but done to prevent leakage of the project names
not_found! ( 'Service' ) unless project
2017-09-10 17:25:29 +05:30
service = slash_command_service ( project , service_slug , params )
2017-08-17 22:00:37 +05:30
result = service . try ( :trigger , params )
if result
status result [ :status ] || 200
present result
else
not_found! ( 'Service' )
end
end
2015-04-26 12:48:37 +05:30
end
2014-09-02 18:07:02 +05:30
end
end
end