summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/api.rb13
-rw-r--r--lib/api/award_emoji.rb20
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/api/entities.rb70
-rw-r--r--lib/api/groups.rb6
-rw-r--r--lib/api/helpers.rb39
-rw-r--r--lib/api/issues.rb36
-rw-r--r--lib/api/jobs.rb (renamed from lib/api/builds.rb)130
-rw-r--r--lib/api/merge_request_diffs.rb12
-rw-r--r--lib/api/merge_requests.rb56
-rw-r--r--lib/api/milestones.rb15
-rw-r--r--lib/api/pipelines.rb4
-rw-r--r--lib/api/project_snippets.rb16
-rw-r--r--lib/api/projects.rb15
-rw-r--r--lib/api/services.rb10
-rw-r--r--lib/api/settings.rb10
-rw-r--r--lib/api/snippets.rb16
-rw-r--r--lib/api/time_tracking_endpoints.rb6
-rw-r--r--lib/api/todos.rb8
-rw-r--r--lib/api/triggers.rb76
-rw-r--r--lib/api/v3/award_emoji.rb79
-rw-r--r--lib/api/v3/builds.rb263
-rw-r--r--lib/api/v3/commits.rb4
-rw-r--r--lib/api/v3/deployments.rb43
-rw-r--r--lib/api/v3/entities.rb125
-rw-r--r--lib/api/v3/environments.rb62
-rw-r--r--lib/api/v3/groups.rb147
-rw-r--r--lib/api/v3/helpers.rb19
-rw-r--r--lib/api/v3/merge_request_diffs.rb43
-rw-r--r--lib/api/v3/merge_requests.rb8
-rw-r--r--lib/api/v3/milestones.rb64
-rw-r--r--lib/api/v3/pipelines.rb36
-rw-r--r--lib/api/v3/project_hooks.rb106
-rw-r--r--lib/api/v3/services.rb68
-rw-r--r--lib/api/v3/settings.rb137
-rw-r--r--lib/api/v3/snippets.rb138
-rw-r--r--lib/api/v3/time_tracking_endpoints.rb116
-rw-r--r--lib/api/v3/triggers.rb77
-rw-r--r--lib/backup/manager.rb3
-rw-r--r--lib/backup/repository.rb5
-rw-r--r--lib/banzai/filter/emoji_filter.rb64
-rw-r--r--lib/banzai/filter/sanitization_filter.rb4
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb2
-rw-r--r--lib/gitlab/auth.rb21
-rw-r--r--lib/gitlab/auth/too_many_ips.rb17
-rw-r--r--lib/gitlab/auth/unique_ips_limiter.rb43
-rw-r--r--lib/gitlab/award_emoji.rb84
-rw-r--r--lib/gitlab/ci/config/entry/cache.rb6
-rw-r--r--lib/gitlab/ci/config/entry/job.rb11
-rw-r--r--lib/gitlab/ci/config/entry/key.rb4
-rw-r--r--lib/gitlab/ci/config/entry/node.rb6
-rw-r--r--lib/gitlab/ci/config/entry/undefined.rb4
-rw-r--r--lib/gitlab/ci/status/build/play.rb12
-rw-r--r--lib/gitlab/ci/status/build/stop.rb12
-rw-r--r--lib/gitlab/ci/status/manual.rb19
-rw-r--r--lib/gitlab/data_builder/pipeline.rb2
-rw-r--r--lib/gitlab/database.rb6
-rw-r--r--lib/gitlab/emoji.rb43
-rw-r--r--lib/gitlab/etag_caching/middleware.rb66
-rw-r--r--lib/gitlab/etag_caching/store.rb32
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--lib/gitlab/request_context.rb21
-rw-r--r--lib/gitlab/seeder.rb19
-rw-r--r--lib/gitlab/sidekiq_status.rb35
-rw-r--r--lib/gitlab/visibility_level.rb39
-rw-r--r--lib/gitlab/workhorse.rb13
-rw-r--r--lib/mattermost/client.rb2
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--lib/mattermost/team.rb13
-rw-r--r--lib/support/nginx/gitlab-ssl3
-rw-r--r--lib/tasks/gemojione.rake88
-rw-r--r--lib/tasks/gitlab/info.rake3
73 files changed, 2308 insertions, 496 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb
index b27ac3f1d15..1bf20f76ad6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -5,10 +5,13 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
+ helpers ::API::V3::Helpers
+
mount ::API::V3::AwardEmoji
mount ::API::V3::Boards
mount ::API::V3::Branches
mount ::API::V3::BroadcastMessages
+ mount ::API::V3::Builds
mount ::API::V3::Commits
mount ::API::V3::DeployKeys
mount ::API::V3::Environments
@@ -20,12 +23,16 @@ module API
mount ::API::V3::MergeRequestDiffs
mount ::API::V3::MergeRequests
mount ::API::V3::Notes
+ mount ::API::V3::Pipelines
mount ::API::V3::ProjectHooks
+ mount ::API::V3::Milestones
mount ::API::V3::Projects
mount ::API::V3::ProjectSnippets
mount ::API::V3::Repositories
mount ::API::V3::Runners
mount ::API::V3::Services
+ mount ::API::V3::Settings
+ mount ::API::V3::Snippets
mount ::API::V3::Subscriptions
mount ::API::V3::SystemHooks
mount ::API::V3::Tags
@@ -56,6 +63,10 @@ module API
error! e.message, e.status, e.headers
end
+ rescue_from Gitlab::Auth::TooManyIps do |e|
+ rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
+ end
+
rescue_from :all do |exception|
handle_api_exception(exception)
end
@@ -73,7 +84,6 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
- mount ::API::Builds
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::DeployKeys
@@ -83,6 +93,7 @@ module API
mount ::API::Groups
mount ::API::Internal
mount ::API::Issues
+ mount ::API::Jobs
mount ::API::Keys
mount ::API::Labels
mount ::API::Lint
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index 07a1bcdbe18..f9e0c2c4e16 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -3,12 +3,16 @@ module API
include PaginationParams
before { authenticate! }
- AWARDABLES = %w[issue merge_request snippet].freeze
+ AWARDABLES = [
+ { type: 'issue', find_by: :iid },
+ { type: 'merge_request', find_by: :iid },
+ { type: 'snippet', find_by: :id }
+ ].freeze
resource :projects do
- AWARDABLES.each do |awardable_type|
- awardable_string = awardable_type.pluralize
- awardable_id_string = "#{awardable_type}_id"
+ AWARDABLES.each do |awardable_params|
+ awardable_string = awardable_params[:type].pluralize
+ awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}"
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -104,10 +108,10 @@ module API
note_id = params.delete(:note_id)
awardable.notes.find(note_id)
- elsif params.include?(:issue_id)
- user_project.issues.find(params[:issue_id])
- elsif params.include?(:merge_request_id)
- user_project.merge_requests.find(params[:merge_request_id])
+ elsif params.include?(:issue_iid)
+ user_project.issues.find_by!(iid: params[:issue_iid])
+ elsif params.include?(:merge_request_iid)
+ user_project.merge_requests.find_by!(iid: params[:merge_request_iid])
else
user_project.snippets.find(params[:snippet_id])
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index fd03e92264d..b0aa10f8bf2 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -127,7 +127,7 @@ module API
commit_params = {
commit: commit,
- create_merge_request: false,
+ start_branch: params[:branch],
target_branch: params[:branch]
}
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index c5feb49b22f..2f1ad12c38c 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -1,5 +1,5 @@
module API
- # Deployments RESTfull API endpoints
+ # Deployments RESTful API endpoints
class Deployments < Grape::API
include PaginationParams
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index f961102b910..c8f21fc9ca8 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -49,7 +49,8 @@ module API
class ProjectHook < Hook
expose :project_id, :issues_events, :merge_requests_events
- expose :note_events, :build_events, :pipeline_events, :wiki_page_events
+ expose :note_events, :pipeline_events, :wiki_page_events
+ expose :build_events, as: :job_events
end
class BasicProjectDetails < Grape::Entity
@@ -69,9 +70,8 @@ module API
class Project < Grape::Entity
expose :id, :description, :default_branch, :tag_list
- expose :public?, as: :public
expose :archived?, as: :archived
- expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
+ expose :visibility, :ssh_url_to_repo, :http_url_to_repo, :web_url
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace
expose :path, :path_with_namespace
@@ -81,7 +81,7 @@ module API
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
- expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
+ expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose :created_at, :last_activity_at
@@ -94,7 +94,7 @@ module API
expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
- expose :public_builds
+ expose :public_builds, as: :public_jobs
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
@@ -110,7 +110,7 @@ module API
expose :storage_size
expose :repository_size
expose :lfs_objects_size
- expose :build_artifacts_size
+ expose :build_artifacts_size, as: :job_artifacts_size
end
class Member < UserBasic
@@ -132,7 +132,7 @@ module API
end
class Group < Grape::Entity
- expose :id, :name, :path, :description, :visibility_level
+ expose :id, :name, :path, :description, :visibility
expose :lfs_enabled?, as: :lfs_enabled
expose :avatar_url
expose :web_url
@@ -145,7 +145,7 @@ module API
expose :storage_size
expose :repository_size
expose :lfs_objects_size
- expose :build_artifacts_size
+ expose :build_artifacts_size, as: :job_artifacts_size
end
end
end
@@ -250,14 +250,11 @@ module API
expose :start_date
end
- class Issue < ProjectEntity
+ class IssueBasic < ProjectEntity
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :subscribed do |issue, options|
- issue.subscribed?(options[:current_user], options[:project] || issue.project)
- end
expose :user_notes_count
expose :upvotes, :downvotes
expose :due_date
@@ -268,6 +265,12 @@ module API
end
end
+ class Issue < IssueBasic
+ expose :subscribed do |issue, options|
+ issue.subscribed?(options[:current_user], options[:project] || issue.project)
+ end
+ end
+
class IssuableTimeStats < Grape::Entity
expose :time_estimate
expose :total_time_spent
@@ -280,7 +283,7 @@ module API
expose :id
end
- class MergeRequest < ProjectEntity
+ class MergeRequestBasic < ProjectEntity
expose :target_branch, :source_branch
expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
@@ -292,9 +295,6 @@ module API
expose :merge_status
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
- expose :subscribed do |merge_request, options|
- merge_request.subscribed?(options[:current_user], options[:project])
- end
expose :user_notes_count
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
@@ -304,6 +304,12 @@ module API
end
end
+ class MergeRequest < MergeRequestBasic
+ expose :subscribed do |merge_request, options|
+ merge_request.subscribed?(options[:current_user], options[:project])
+ end
+ end
+
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
compare.raw_diffs(all_diffs: true).to_a
@@ -449,7 +455,8 @@ module API
class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :merge_requests_events
- expose :tag_push_events, :note_events, :build_events, :pipeline_events
+ expose :tag_push_events, :note_events, :pipeline_events
+ expose :build_events, as: :job_events
# Expose serialized properties
expose :properties do |service, options|
field_names = service.fields.
@@ -552,12 +559,14 @@ module API
expose :updated_at
expose :home_page_url
expose :default_branch_protection
- expose :restricted_visibility_levels
+ expose(:restricted_visibility_levels) do |setting, _options|
+ setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) }
+ end
expose :max_attachment_size
expose :session_expire_delay
- expose :default_project_visibility
- expose :default_snippet_visibility
- expose :default_group_visibility
+ expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) }
+ expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
+ expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
expose :default_artifacts_expire_in
expose :domain_whitelist
expose :domain_blacklist_enabled
@@ -591,10 +600,6 @@ module API
end
end
- class TriggerRequest < Grape::Entity
- expose :id, :variables
- end
-
class Runner < Grape::Entity
expose :id
expose :description
@@ -623,7 +628,7 @@ module API
expose :id, :token
end
- class BuildArtifactFile < Grape::Entity
+ class JobArtifactFile < Grape::Entity
expose :filename, :size
end
@@ -631,18 +636,21 @@ module API
expose :id, :sha, :ref, :status
end
- class Build < Grape::Entity
+ class Job < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
expose :user, with: User
- expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
+ expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
expose :commit, with: RepoCommit
expose :runner, with: Runner
expose :pipeline, with: PipelineBasic
end
class Trigger < Grape::Entity
- expose :token, :created_at, :updated_at, :deleted_at, :last_used
+ expose :id
+ expose :token, :description
+ expose :created_at, :updated_at, :deleted_at, :last_used
+ expose :owner, using: Entities::UserBasic
end
class Variable < Grape::Entity
@@ -663,14 +671,14 @@ module API
end
class Environment < EnvironmentBasic
- expose :project, using: Entities::Project
+ expose :project, using: Entities::BasicProjectDetails
end
class Deployment < Grape::Entity
expose :id, :iid, :ref, :sha, :created_at
expose :user, using: Entities::UserBasic
expose :environment, using: Entities::EnvironmentBasic
- expose :deployable, using: Entities::Build
+ expose :deployable, using: Entities::Job
end
class RepoLicense < Grape::Entity
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 9cffd6180ae..b862ff70b31 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -7,7 +7,7 @@ module API
helpers do
params :optional_params do
optional :description, type: String, desc: 'The description of the group'
- optional :visibility_level, type: Integer, desc: 'The visibility level of the group'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the group'
optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
end
@@ -92,7 +92,7 @@ module API
optional :name, type: String, desc: 'The name of the group'
optional :path, type: String, desc: 'The path of the group'
use :optional_params
- at_least_one_of :name, :path, :description, :visibility_level,
+ at_least_one_of :name, :path, :description, :visibility,
:lfs_enabled, :request_access_enabled
end
put ':id' do
@@ -126,7 +126,7 @@ module API
end
params do
optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
- optional :visibility, type: String, values: %w[public internal private],
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 4600abc7dc7..a9b364da9e1 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -82,16 +82,16 @@ module API
label || not_found!('Label')
end
- def find_project_issue(id)
- IssuesFinder.new(current_user, project_id: user_project.id).find(id)
+ def find_project_issue(iid)
+ IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end
- def find_project_merge_request(id)
- MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
+ def find_project_merge_request(iid)
+ MergeRequestsFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
end
- def find_merge_request_with_access(id, access_level = :read_merge_request)
- merge_request = user_project.merge_requests.find(id)
+ def find_merge_request_with_access(iid, access_level = :read_merge_request)
+ merge_request = user_project.merge_requests.find_by!(iid: iid)
authorize! access_level, merge_request
merge_request
end
@@ -252,6 +252,10 @@ module API
# project helpers
def filter_projects(projects)
+ if params[:membership]
+ projects = projects.merge(current_user.authorized_projects)
+ end
+
if params[:owned]
projects = projects.merge(current_user.owned_projects)
end
@@ -332,16 +336,17 @@ module API
def initial_current_user
return @initial_current_user if defined?(@initial_current_user)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user! do
+ @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
+ @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
+ @initial_current_user ||= find_user_from_warden
- @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
- @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
- @initial_current_user ||= find_user_from_warden
+ unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
+ @initial_current_user = nil
+ end
- unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
- @initial_current_user = nil
+ @initial_current_user
end
-
- @initial_current_user
end
def sudo!
@@ -384,14 +389,6 @@ module API
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
end
- def issue_entity(project)
- if project.has_external_issue_tracker?
- Entities::ExternalIssue
- else
- Entities::Issue
- end
- end
-
# The Grape Error Middleware only has access to env but no params. We workaround this by
# defining a method that returns the right value.
def define_params_for_grape_middleware
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1d6d0b05750..4a9f2b26fb2 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -41,7 +41,7 @@ module API
resource :issues do
desc "Get currently authenticated user's issues" do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
@@ -51,7 +51,7 @@ module API
get do
issues = find_issues(scope: 'authored')
- present paginate(issues), with: Entities::Issue, current_user: current_user
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
end
@@ -60,7 +60,7 @@ module API
end
resource :groups do
desc 'Get a list of group issues' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'opened',
@@ -72,7 +72,7 @@ module API
issues = find_issues(group_id: group.id, state: params[:state] || 'opened')
- present paginate(issues), with: Entities::Issue, current_user: current_user
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
end
@@ -83,7 +83,7 @@ module API
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
@@ -95,17 +95,17 @@ module API
issues = find_issues(project_id: project.id)
- present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
end
desc 'Get a single project issue' do
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- get ":id/issues/:issue_id" do
- issue = find_project_issue(params[:issue_id])
+ get ":id/issues/:issue_iid" do
+ issue = find_project_issue(params[:issue_iid])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
@@ -152,7 +152,7 @@ module API
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
@@ -161,8 +161,8 @@ module API
at_least_one_of :title, :description, :assignee_id, :milestone_id,
:labels, :created_at, :due_date, :confidential, :state_event
end
- put ':id/issues/:issue_id' do
- issue = user_project.issues.find(params.delete(:issue_id))
+ put ':id/issues/:issue_iid' do
+ issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
# Setting created_at time only allowed for admins and project owners
@@ -189,11 +189,11 @@ module API
success Entities::Issue
end
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
- post ':id/issues/:issue_id/move' do
- issue = user_project.issues.find_by(id: params[:issue_id])
+ post ':id/issues/:issue_iid/move' do
+ issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id])
@@ -209,10 +209,10 @@ module API
desc 'Delete a project issue'
params do
- requires :issue_id, type: Integer, desc: 'The ID of a project issue'
+ requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue'
end
- delete ":id/issues/:issue_id" do
- issue = user_project.issues.find_by(id: params[:issue_id])
+ delete ":id/issues/:issue_iid" do
+ issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
diff --git a/lib/api/builds.rb b/lib/api/jobs.rb
index 5b76913fe45..33c05e8aa63 100644
--- a/lib/api/builds.rb
+++ b/lib/api/jobs.rb
@@ -1,5 +1,5 @@
module API
- class Builds < Grape::API
+ class Jobs < Grape::API
include PaginationParams
before { authenticate! }
@@ -13,9 +13,10 @@ module API
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
values: ::CommitStatus::AVAILABLE_STATUSES,
coerce_with: ->(scope) {
- if scope.is_a?(String)
+ case scope
+ when String
[scope]
- elsif scope.is_a?(Hashie::Mash)
+ when Hashie::Mash
scope.values
else
['unknown']
@@ -24,79 +25,58 @@ module API
end
end
- desc 'Get a project builds' do
- success Entities::Build
+ desc 'Get a projects jobs' do
+ success Entities::Job
end
params do
use :optional_scope
use :pagination
end
- get ':id/builds' do
+ get ':id/jobs' do
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
- present paginate(builds), with: Entities::Build,
+ present paginate(builds), with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Get builds for a specific commit of a project' do
- success Entities::Build
+ desc 'Get a specific job of a project' do
+ success Entities::Job
end
params do
- requires :sha, type: String, desc: 'The SHA id of a commit'
- use :optional_scope
- use :pagination
- end
- get ':id/repository/commits/:sha/builds' do
- authorize_read_builds!
-
- return not_found! unless user_project.commit(params[:sha])
-
- pipelines = user_project.pipelines.where(sha: params[:sha])
- builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
- builds = filter_builds(builds, params[:scope])
-
- present paginate(builds), with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
- end
-
- desc 'Get a specific build of a project' do
- success Entities::Build
- end
- params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id' do
+ get ':id/jobs/:job_id' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Download the artifacts file from build' do
+ desc 'Download the artifacts file from a job' do
detail 'This feature was introduced in GitLab 8.5'
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id/artifacts' do
+ get ':id/jobs/:job_id/artifacts' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
present_artifacts!(build.artifacts_file)
end
- desc 'Download the artifacts file from build' do
+ desc 'Download the artifacts file from a job' do
detail 'This feature was introduced in GitLab 8.10'
end
params do
requires :ref_name, type: String, desc: 'The ref from repository'
- requires :job, type: String, desc: 'The name for the build'
+ requires :job, type: String, desc: 'The name for the job'
end
- get ':id/builds/artifacts/:ref_name/download',
+ get ':id/jobs/artifacts/:ref_name/download',
requirements: { ref_name: /.+/ } do
authorize_read_builds!
@@ -109,14 +89,14 @@ module API
# TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
- desc 'Get a trace of a specific build of a project'
+ desc 'Get a trace of a specific job of a project'
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id/trace' do
+ get ':id/jobs/:job_id/trace' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
content_type 'text/plain'
@@ -126,95 +106,95 @@ module API
body trace
end
- desc 'Cancel a specific build of a project' do
- success Entities::Build
+ desc 'Cancel a specific job of a project' do
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- post ':id/builds/:build_id/cancel' do
+ post ':id/jobs/:job_id/cancel' do
authorize_update_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
build.cancel
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
desc 'Retry a specific build of a project' do
- success Entities::Build
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/builds/:build_id/retry' do
+ post ':id/jobs/:job_id/retry' do
authorize_update_builds!
- build = get_build!(params[:build_id])
- return forbidden!('Build is not retryable') unless build.retryable?
+ build = get_build!(params[:job_id])
+ return forbidden!('Job is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user)
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Erase build (remove artifacts and build trace)' do
- success Entities::Build
+ desc 'Erase job (remove artifacts and the trace)' do
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/builds/:build_id/erase' do
+ post ':id/jobs/:job_id/erase' do
authorize_update_builds!
- build = get_build!(params[:build_id])
- return forbidden!('Build is not erasable!') unless build.erasable?
+ build = get_build!(params[:job_id])
+ return forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
end
desc 'Keep the artifacts to prevent them from being deleted' do
- success Entities::Build
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- post ':id/builds/:build_id/artifacts/keep' do
+ post ':id/jobs/:job_id/artifacts/keep' do
authorize_update_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
return not_found!(build) unless build.artifacts?
build.keep_artifacts!
status 200
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Trigger a manual build' do
- success Entities::Build
+ desc 'Trigger a manual job' do
+ success Entities::Job
detail 'This feature was added in GitLab 8.11'
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a Build'
+ requires :job_id, type: Integer, desc: 'The ID of a Job'
end
- post ":id/builds/:build_id/play" do
+ post ":id/jobs/:job_id/play" do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
bad_request!("Unplayable Job") unless build.playable?
build.play(current_user)
status 200
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
end
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 4901a7cfea6..a59e39cca26 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -13,11 +13,11 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
use :pagination
end
- get ":id/merge_requests/:merge_request_id/versions" do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ":id/merge_requests/:merge_request_iid/versions" do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.merge_request_diffs), with: Entities::MergeRequestDiff
end
@@ -29,12 +29,12 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
end
- get ":id/merge_requests/:merge_request_id/versions/:version_id" do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4638a66811d..7a03955a045 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -25,6 +25,14 @@ module API
render_api_error!(errors, 400)
end
+ def issue_entity(project)
+ if project.has_external_issue_tracker?
+ Entities::ExternalIssue
+ else
+ Entities::IssueBasic
+ end
+ end
+
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
@@ -35,7 +43,7 @@ module API
end
desc 'List merge requests' do
- success Entities::MergeRequest
+ success Entities::MergeRequestBasic
end
params do
optional :state, type: String, values: %w[opened closed merged all], default: 'all',
@@ -62,7 +70,7 @@ module API
end
merge_requests = merge_requests.reorder(params[:order_by] => params[:sort])
- present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
desc 'Create a merge request' do
@@ -93,23 +101,23 @@ module API
desc 'Delete a merge request'
params do
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
- delete ":id/merge_requests/:merge_request_id" do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ delete ":id/merge_requests/:merge_request_iid" do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
merge_request.destroy
end
params do
- requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
desc 'Get a single merge request' do
success Entities::MergeRequest
end
- get ':id/merge_requests/:merge_request_id' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project
end
@@ -117,8 +125,8 @@ module API
desc 'Get the commits of a merge request' do
success Entities::RepoCommit
end
- get ':id/merge_requests/:merge_request_id/commits' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/commits' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
commits = ::Kaminari.paginate_array(merge_request.commits)
present paginate(commits), with: Entities::RepoCommit
@@ -127,8 +135,8 @@ module API
desc 'Show the merge request changes' do
success Entities::MergeRequestChanges
end
- get ':id/merge_requests/:merge_request_id/changes' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/changes' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present merge_request, with: Entities::MergeRequestChanges, current_user: current_user
end
@@ -146,8 +154,8 @@ module API
:milestone_id, :labels, :state_event,
:remove_source_branch
end
- put ':id/merge_requests/:merge_request_id' do
- merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
+ put ':id/merge_requests/:merge_request_iid' do
+ merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
@@ -172,8 +180,8 @@ module API
desc: 'When true, this merge request will be merged when the pipeline succeeds'
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
end
- put ':id/merge_requests/:merge_request_id/merge' do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ put ':id/merge_requests/:merge_request_iid/merge' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
# Merge request can not be merged
# because user dont have permissions to push into target branch
@@ -208,8 +216,8 @@ module API
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
- post ':id/merge_requests/:merge_request_id/cancel_merge_when_pipeline_succeeds' do
- merge_request = find_project_merge_request(params[:merge_request_id])
+ post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
+ merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_cancel_merge_when_pipeline_succeeds?(current_user)
@@ -224,8 +232,8 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_id/comments' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/comments' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
present paginate(merge_request.notes.fresh), with: Entities::MRNote
end
@@ -235,8 +243,8 @@ module API
params do
requires :note, type: String, desc: 'The text of the comment'
end
- post ':id/merge_requests/:merge_request_id/comments' do
- merge_request = find_merge_request_with_access(params[:merge_request_id], :create_note)
+ post ':id/merge_requests/:merge_request_iid/comments' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid], :create_note)
opts = {
note: params[:note],
@@ -259,8 +267,8 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_id/closes_issues' do
- merge_request = find_merge_request_with_access(params[:merge_request_id])
+ get ':id/merge_requests/:merge_request_iid/closes_issues' do
+ merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user))
present paginate(issues), with: issue_entity(user_project), current_user: current_user
end
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 44bdaea7fa4..e7f7edd95c7 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -30,7 +30,7 @@ module API
params do
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
- optional :iid, type: Array[Integer], desc: 'The IID of the milestone'
+ optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
end
@@ -39,7 +39,7 @@ module API
milestones = user_project.milestones
milestones = filter_milestones_state(milestones, params[:state])
- milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present?
+ milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone
@@ -103,7 +103,7 @@ module API
end
desc 'Get all issues for a single project milestone' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
@@ -120,12 +120,12 @@ module API
}
issues = IssuesFinder.new(current_user, finder_params).execute
- present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
end
desc 'Get all merge requests for a single project milestone' do
detail 'This feature was introduced in GitLab 9.'
- success Entities::MergeRequest
+ success Entities::MergeRequestBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
@@ -142,7 +142,10 @@ module API
}
merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
- present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ present paginate(merge_requests),
+ with: Entities::MergeRequestBasic,
+ current_user: current_user,
+ project: user_project
end
end
end
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 3afc1e385fe..0721b975ba4 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -10,7 +10,7 @@ module API
resource :projects do
desc 'Get all Pipelines of the project' do
detail 'This feature was introduced in GitLab 8.11.'
- success Entities::Pipeline
+ success Entities::PipelineBasic
end
params do
use :pagination
@@ -21,7 +21,7 @@ module API
authorize! :read_pipeline, user_project
pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
- present paginate(pipelines), with: Entities::Pipeline
+ present paginate(pipelines), with: Entities::PipelineBasic
end
desc 'Create a new pipeline' do
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 2a1cce73f3f..f57e7ea4032 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -50,11 +50,9 @@ module API
requires :title, type: String, desc: 'The title of the snippet'
requires :file_name, type: String, desc: 'The file name of the snippet'
requires :code, type: String, desc: 'The content of the snippet'
- requires :visibility_level, type: Integer,
- values: [Gitlab::VisibilityLevel::PRIVATE,
- Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC],
- desc: 'The visibility level of the snippet'
+ requires :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ desc: 'The visibility of the snippet'
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
@@ -80,11 +78,9 @@ module API
optional :title, type: String, desc: 'The title of the snippet'
optional :file_name, type: String, desc: 'The file name of the snippet'
optional :code, type: String, desc: 'The content of the snippet'
- optional :visibility_level, type: Integer,
- values: [Gitlab::VisibilityLevel::PRIVATE,
- Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC],
- desc: 'The visibility level of the snippet'
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ desc: 'The visibility of the snippet'
at_least_one_of :title, :file_name, :code, :visibility_level
end
put ":id/snippets/:snippet_id" do
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 996404e0e49..63a4cdd5954 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -16,11 +16,7 @@ module API
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
- optional :visibility_level, type: Integer, values: [
- Gitlab::VisibilityLevel::PRIVATE,
- Gitlab::VisibilityLevel::INTERNAL,
- Gitlab::VisibilityLevel::PUBLIC
- ], desc: 'Create a public project. The same as visibility_level = 20.'
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed'
@@ -48,11 +44,12 @@ module API
params :filter_params do
optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
- optional :visibility, type: String, values: %w[public internal private],
+ optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values,
desc: 'Limit by visibility'
- optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+ optional :search, type: String, desc: 'Return list of projects matching the search criteria'
optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+ optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of'
end
params :statistics_params do
@@ -208,7 +205,7 @@ module API
at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled,
:wiki_enabled, :builds_enabled, :snippets_enabled,
:shared_runners_enabled, :container_registry_enabled,
- :lfs_enabled, :visibility_level, :public_builds,
+ :lfs_enabled, :visibility, :public_builds,
:request_access_enabled, :only_allow_merge_if_pipeline_succeeds,
:only_allow_merge_if_all_discussions_are_resolved, :path,
:default_branch
@@ -217,7 +214,7 @@ module API
authorize_admin_project
attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
- authorize! :change_visibility_level, user_project if attrs[:visibility_level].present?
+ authorize! :change_visibility_level, user_project if attrs[:visibility].present?
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 79a5f27dc4d..1cf29d9a1a3 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -122,9 +122,9 @@ module API
},
{
required: false,
- name: :notify_only_broken_builds,
+ name: :notify_only_broken_jobs,
type: Boolean,
- desc: 'Notify only broken builds'
+ desc: 'Notify only broken jobs'
}
],
'campfire' => [
@@ -403,9 +403,9 @@ module API
},
{
required: false,
- name: :notify_only_broken_builds,
+ name: :notify_only_broken_jobs,
type: Boolean,
- desc: 'Notify only broken builds'
+ desc: 'Notify only broken jobs'
}
],
'pivotaltracker' => [
@@ -611,7 +611,7 @@ module API
desc "Set #{service_slug} service for project"
params do
service_classes.each do |service|
- event_names = service.try(:event_names) || []
+ event_names = service.try(:event_names) || next
event_names.each do |event_name|
services[service.to_param.tr("_", "-")] << {
required: false,
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 936c7e0930b..d4d3229f0d1 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -21,9 +21,9 @@ module API
end
params do
optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
- optional :default_project_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default project visibility'
- optional :default_snippet_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default snippet visibility'
- optional :default_group_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default group visibility'
+ optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
+ optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility'
+ optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
@@ -128,7 +128,9 @@ module API
:housekeeping_enabled, :terminal_max_session_time
end
put "application/settings" do
- if current_settings.update_attributes(declared_params(include_missing: false))
+ attrs = declared_params(include_missing: false)
+
+ if current_settings.update_attributes(attrs)
present current_settings, with: Entities::ApplicationSetting
else
render_validation_error!(current_settings)
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index 0f86fdb3075..b93fdc62808 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -58,10 +58,10 @@ module API
requires :title, type: String, desc: 'The title of a snippet'
requires :file_name, type: String, desc: 'The name of a snippet file'
requires :content, type: String, desc: 'The content of a snippet'
- optional :visibility_level, type: Integer,
- values: Gitlab::VisibilityLevel.values,
- default: Gitlab::VisibilityLevel::INTERNAL,
- desc: 'The visibility level of the snippet'
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ default: 'internal',
+ desc: 'The visibility of the snippet'
end
post do
attrs = declared_params(include_missing: false).merge(request: request, api: true)
@@ -85,10 +85,10 @@ module API
optional :title, type: String, desc: 'The title of a snippet'
optional :file_name, type: String, desc: 'The name of a snippet file'
optional :content, type: String, desc: 'The content of a snippet'
- optional :visibility_level, type: Integer,
- values: Gitlab::VisibilityLevel.values,
- desc: 'The visibility level of the snippet'
- at_least_one_of :title, :file_name, :content, :visibility_level
+ optional :visibility, type: String,
+ values: Gitlab::VisibilityLevel.string_values,
+ desc: 'The visibility of the snippet'
+ at_least_one_of :title, :file_name, :content, :visibility
end
put ':id' do
snippet = snippets_for_current_user.find_by(id: params.delete(:id))
diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb
index 85b5f7d98b8..05b4b490e27 100644
--- a/lib/api/time_tracking_endpoints.rb
+++ b/lib/api/time_tracking_endpoints.rb
@@ -5,11 +5,11 @@ module API
included do
helpers do
def issuable_name
- declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request'
+ declared_params.has_key?(:issue_iid) ? 'issue' : 'merge_request'
end
def issuable_key
- "#{issuable_name}_id".to_sym
+ "#{issuable_name}_iid".to_sym
end
def update_issuable_key
@@ -50,7 +50,7 @@ module API
issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
issuable_collection_name = issuable_name.pluralize
- issuable_key = "#{issuable_name}_id".to_sym
+ issuable_key = "#{issuable_name}_iid".to_sym
desc "Set a time estimate for a project #{issuable_name}"
params do
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index e59030428da..d9b8837a5bb 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -5,8 +5,8 @@ module API
before { authenticate! }
ISSUABLE_TYPES = {
- 'merge_requests' => ->(id) { find_merge_request_with_access(id) },
- 'issues' => ->(id) { find_project_issue(id) }
+ 'merge_requests' => ->(iid) { find_merge_request_with_access(iid) },
+ 'issues' => ->(iid) { find_project_issue(iid) }
}.freeze
params do
@@ -14,13 +14,13 @@ module API
end
resource :projects do
ISSUABLE_TYPES.each do |type, finder|
- type_id_str = "#{type.singularize}_id".to_sym
+ type_id_str = "#{type.singularize}_iid".to_sym
desc 'Create a todo on an issuable' do
success Entities::Todo
end
params do
- requires type_id_str, type: Integer, desc: 'The ID of an issuable'
+ requires type_id_str, type: Integer, desc: 'The IID of an issuable'
end
post ":id/#{type}/:#{type_id_str}/todo" do
issuable = instance_exec(params[type_id_str], &finder)
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index b7c9c5f2b7f..119e9024712 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -6,15 +6,15 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
- desc 'Trigger a GitLab project build' do
- success Entities::TriggerRequest
+ desc 'Trigger a GitLab project pipeline' do
+ success Entities::Pipeline
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
- post ":id/(ref/:ref/)trigger/builds" do
+ post ":id/(ref/:ref/)trigger/pipeline" do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
@@ -29,9 +29,9 @@ module API
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
- present trigger_request, with: Entities::TriggerRequest
+ present trigger_request.pipeline, with: Entities::Pipeline
else
- errors = 'No builds created'
+ errors = 'No pipeline created'
render_api_error!(errors, 400)
end
end
@@ -55,13 +55,13 @@ module API
success Entities::Trigger
end
params do
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
- get ':id/triggers/:token' do
+ get ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
present trigger, with: Entities::Trigger
@@ -70,26 +70,76 @@ module API
desc 'Create a trigger' do
success Entities::Trigger
end
+ params do
+ requires :description, type: String, desc: 'The trigger description'
+ end
post ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.create
+ trigger = user_project.triggers.create(
+ declared_params(include_missing: false).merge(owner: current_user))
- present trigger, with: Entities::Trigger
+ if trigger.valid?
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Update a trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ optional :description, type: String, desc: 'The trigger description'
+ end
+ put ':id/triggers/:trigger_id' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ return not_found!('Trigger') unless trigger
+
+ if trigger.update(declared_params(include_missing: false))
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Take ownership of trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ end
+ post ':id/triggers/:trigger_id/take_ownership' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ return not_found!('Trigger') unless trigger
+
+ if trigger.update(owner: current_user)
+ status :ok
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
end
desc 'Delete a trigger' do
success Entities::Trigger
end
params do
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
- delete ':id/triggers/:token' do
+ delete ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
trigger.destroy
diff --git a/lib/api/v3/award_emoji.rb b/lib/api/v3/award_emoji.rb
index 1e35283631f..cf9e1551f60 100644
--- a/lib/api/v3/award_emoji.rb
+++ b/lib/api/v3/award_emoji.rb
@@ -16,11 +16,64 @@ module API
requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet"
end
- [":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
- ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"].each do |endpoint|
+ [
+ ":id/#{awardable_string}/:#{awardable_id_string}/award_emoji",
+ ":id/#{awardable_string}/:#{awardable_id_string}/notes/:note_id/award_emoji"
+ ].each do |endpoint|
+
+ desc 'Get a list of project +awardable+ award emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ use :pagination
+ end
+ get endpoint do
+ if can_read_awardable?
+ awards = awardable.award_emoji
+ present paginate(awards), with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji")
+ end
+ end
+
+ desc 'Get a specific award emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ requires :award_id, type: Integer, desc: 'The ID of the award'
+ end
+ get "#{endpoint}/:award_id" do
+ if can_read_awardable?
+ present awardable.award_emoji.find(params[:award_id]), with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji")
+ end
+ end
+
+ desc 'Award a new Emoji' do
+ detail 'This feature was introduced in 8.9'
+ success Entities::AwardEmoji
+ end
+ params do
+ requires :name, type: String, desc: 'The name of a award_emoji (without colons)'
+ end
+ post endpoint do
+ not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
+
+ award = awardable.create_award_emoji(params[:name], current_user)
+
+ if award.persisted?
+ present award, with: Entities::AwardEmoji
+ else
+ not_found!("Award Emoji #{award.errors.messages}")
+ end
+ end
+
desc 'Delete a +awardables+ award emoji' do
detail 'This feature was introduced in 8.9'
- success ::API::Entities::AwardEmoji
+ success Entities::AwardEmoji
end
params do
requires :award_id, type: Integer, desc: 'The ID of an award emoji'
@@ -30,13 +83,22 @@ module API
unauthorized! unless award.user == current_user || current_user.admin?
- present award.destroy, with: ::API::Entities::AwardEmoji
+ award.destroy
+ present award, with: Entities::AwardEmoji
end
end
end
end
helpers do
+ def can_read_awardable?
+ can?(current_user, read_ability(awardable), awardable)
+ end
+
+ def can_award_awardable?
+ awardable.user_can_award?(current_user, params[:name])
+ end
+
def awardable
@awardable ||=
begin
@@ -53,6 +115,15 @@ module API
end
end
end
+
+ def read_ability(awardable)
+ case awardable
+ when Note
+ read_ability(awardable.noteable)
+ else
+ :"read_#{awardable.class.to_s.underscore}"
+ end
+ end
end
end
end
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
new file mode 100644
index 00000000000..c8feba13527
--- /dev/null
+++ b/lib/api/v3/builds.rb
@@ -0,0 +1,263 @@
+module API
+ module V3
+ class Builds < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ helpers do
+ params :optional_scope do
+ optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
+ values: %w(pending running failed success canceled skipped),
+ coerce_with: ->(scope) {
+ if scope.is_a?(String)
+ [scope]
+ elsif scope.is_a?(Hashie::Mash)
+ scope.values
+ else
+ ['unknown']
+ end
+ }
+ end
+ end
+
+ desc 'Get a project builds' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ use :optional_scope
+ use :pagination
+ end
+ get ':id/builds' do
+ builds = user_project.builds.order('id DESC')
+ builds = filter_builds(builds, params[:scope])
+
+ present paginate(builds), with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Get builds for a specific commit of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :sha, type: String, desc: 'The SHA id of a commit'
+ use :optional_scope
+ use :pagination
+ end
+ get ':id/repository/commits/:sha/builds' do
+ authorize_read_builds!
+
+ return not_found! unless user_project.commit(params[:sha])
+
+ pipelines = user_project.pipelines.where(sha: params[:sha])
+ builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
+ builds = filter_builds(builds, params[:scope])
+
+ present paginate(builds), with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Get a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Download the artifacts file from build' do
+ detail 'This feature was introduced in GitLab 8.5'
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id/artifacts' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ present_artifacts!(build.artifacts_file)
+ end
+
+ desc 'Download the artifacts file from build' do
+ detail 'This feature was introduced in GitLab 8.10'
+ end
+ params do
+ requires :ref_name, type: String, desc: 'The ref from repository'
+ requires :job, type: String, desc: 'The name for the build'
+ end
+ get ':id/builds/artifacts/:ref_name/download',
+ requirements: { ref_name: /.+/ } do
+ authorize_read_builds!
+
+ builds = user_project.latest_successful_builds_for(params[:ref_name])
+ latest_build = builds.find_by!(name: params[:job])
+
+ present_artifacts!(latest_build.artifacts_file)
+ end
+
+ # TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
+ # is saved in the DB instead of file). But before that, we need to consider how to replace the value of
+ # `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
+ desc 'Get a trace of a specific build of a project'
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id/trace' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
+ content_type 'text/plain'
+ env['api.format'] = :binary
+
+ trace = build.trace
+ body trace
+ end
+
+ desc 'Cancel a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/cancel' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+
+ build.cancel
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Retry a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/retry' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return forbidden!('Build is not retryable') unless build.retryable?
+
+ build = Ci::Build.retry(build, current_user)
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Erase build (remove artifacts and build trace)' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/erase' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return forbidden!('Build is not erasable!') unless build.erasable?
+
+ build.erase(erased_by: current_user)
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ end
+
+ desc 'Keep the artifacts to prevent them from being deleted' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/artifacts/keep' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return not_found!(build) unless build.artifacts?
+
+ build.keep_artifacts!
+
+ status 200
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Trigger a manual build' do
+ success ::API::V3::Entities::Build
+ detail 'This feature was added in GitLab 8.11'
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a Build'
+ end
+ post ":id/builds/:build_id/play" do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ bad_request!("Unplayable Job") unless build.playable?
+
+ build.play(current_user)
+
+ status 200
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+ end
+
+ helpers do
+ def get_build(id)
+ user_project.builds.find_by(id: id.to_i)
+ end
+
+ def get_build!(id)
+ get_build(id) || not_found!
+ end
+
+ def present_artifacts!(artifacts_file)
+ if !artifacts_file.file_storage?
+ redirect_to(build.artifacts_file.url)
+ elsif artifacts_file.exists?
+ present_file!(artifacts_file.path, artifacts_file.filename)
+ else
+ not_found!
+ end
+ end
+
+ def filter_builds(builds, scope)
+ return builds if scope.nil? || scope.empty?
+
+ available_statuses = ::CommitStatus::AVAILABLE_STATUSES
+
+ unknown = scope - available_statuses
+ render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
+
+ builds.where(status: available_statuses && scope)
+ end
+
+ def authorize_read_builds!
+ authorize! :read_build, user_project
+ end
+
+ def authorize_update_builds!
+ authorize! :update_build, user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/commits.rb b/lib/api/v3/commits.rb
index 506204b3517..d254d247042 100644
--- a/lib/api/v3/commits.rb
+++ b/lib/api/v3/commits.rb
@@ -130,9 +130,7 @@ module API
commit_params = {
commit: commit,
- create_merge_request: false,
- source_project: user_project,
- source_branch: commit.cherry_pick_branch_name,
+ start_branch: params[:branch],
target_branch: params[:branch]
}
diff --git a/lib/api/v3/deployments.rb b/lib/api/v3/deployments.rb
new file mode 100644
index 00000000000..95114ad1fe1
--- /dev/null
+++ b/lib/api/v3/deployments.rb
@@ -0,0 +1,43 @@
+module API
+ module V3
+ # Deployments RESTful API endpoints
+ class Deployments < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get all deployments of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::V3::Deployments
+ end
+ params do
+ use :pagination
+ end
+ get ':id/deployments' do
+ authorize! :read_deployment, user_project
+
+ present paginate(user_project.deployments), with: ::API::V3::Deployments
+ end
+
+ desc 'Gets a specific deployment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::V3::Deployments
+ end
+ params do
+ requires :deployment_id, type: Integer, desc: 'The deployment ID'
+ end
+ get ':id/deployments/:deployment_id' do
+ authorize! :read_deployment, user_project
+
+ deployment = user_project.deployments.find(params[:deployment_id])
+
+ present deployment, with: ::API::V3::Deployments
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 2a3dcb7f288..832b4bdeb4f 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -81,7 +81,7 @@ module API
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
- expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+ expose :statistics, using: '::API::V3::Entities::ProjectStatistics', if: :statistics
end
class ProjectWithAccess < Project
@@ -125,6 +125,129 @@ module API
Gitlab::UrlBuilder.build(merge_request)
end
end
+
+ class Group < Grape::Entity
+ expose :id, :name, :path, :description, :visibility_level
+ expose :lfs_enabled?, as: :lfs_enabled
+ expose :avatar_url
+ expose :web_url
+ expose :request_access_enabled
+ expose :full_name, :full_path
+ expose :parent_id
+
+ expose :statistics, if: :statistics do
+ with_options format_with: -> (value) { value.to_i } do
+ expose :storage_size
+ expose :repository_size
+ expose :lfs_objects_size
+ expose :build_artifacts_size
+ end
+ end
+ end
+
+ class GroupDetail < Group
+ expose :projects, using: Entities::Project
+ expose :shared_projects, using: Entities::Project
+ end
+
+ class ApplicationSetting < Grape::Entity
+ expose :id
+ expose :default_projects_limit
+ expose :signup_enabled
+ expose :signin_enabled
+ expose :gravatar_enabled
+ expose :sign_in_text
+ expose :after_sign_up_text
+ expose :created_at
+ expose :updated_at
+ expose :home_page_url
+ expose :default_branch_protection
+ expose :restricted_visibility_levels
+ expose :max_attachment_size
+ expose :session_expire_delay
+ expose :default_project_visibility
+ expose :default_snippet_visibility
+ expose :default_group_visibility
+ expose :domain_whitelist
+ expose :domain_blacklist_enabled
+ expose :domain_blacklist
+ expose :user_oauth_applications
+ expose :after_sign_out_path
+ expose :container_registry_token_expire_delay
+ expose :repository_storage
+ expose :repository_storages
+ expose :koding_enabled
+ expose :koding_url
+ expose :plantuml_enabled
+ expose :plantuml_url
+ expose :terminal_max_session_time
+ end
+
+ class Environment < ::API::Entities::EnvironmentBasic
+ expose :project, using: Entities::Project
+ end
+
+ class Trigger < Grape::Entity
+ expose :token, :created_at, :updated_at, :deleted_at, :last_used
+ expose :owner, using: ::API::Entities::UserBasic
+ end
+
+ class TriggerRequest < Grape::Entity
+ expose :id, :variables
+ end
+
+ class Build < Grape::Entity
+ expose :id, :status, :stage, :name, :ref, :tag, :coverage
+ expose :created_at, :started_at, :finished_at
+ expose :user, with: ::API::Entities::User
+ expose :artifacts_file, using: ::API::Entities::JobArtifactFile, if: -> (build, opts) { build.artifacts? }
+ expose :commit, with: ::API::Entities::RepoCommit
+ expose :runner, with: ::API::Entities::Runner
+ expose :pipeline, with: ::API::Entities::PipelineBasic
+ end
+
+ class BuildArtifactFile < Grape::Entity
+ expose :filename, :size
+ end
+
+ class Deployment < Grape::Entity
+ expose :id, :iid, :ref, :sha, :created_at
+ expose :user, using: ::API::Entities::UserBasic
+ expose :environment, using: ::API::Entities::EnvironmentBasic
+ expose :deployable, using: Entities::Build
+ end
+
+ class MergeRequestChanges < MergeRequest
+ expose :diffs, as: :changes, using: ::API::Entities::RepoDiff do |compare, _|
+ compare.raw_diffs(all_diffs: true).to_a
+ end
+ end
+
+ class ProjectStatistics < Grape::Entity
+ expose :commit_count
+ expose :storage_size
+ expose :repository_size
+ expose :lfs_objects_size
+ expose :build_artifacts_size
+ end
+
+ class ProjectService < Grape::Entity
+ expose :id, :title, :created_at, :updated_at, :active
+ expose :push_events, :issues_events, :merge_requests_events
+ expose :tag_push_events, :note_events, :build_events, :pipeline_events
+ # Expose serialized properties
+ expose :properties do |service, options|
+ field_names = service.fields.
+ select { |field| options[:include_passwords] || field[:type] != 'password' }.
+ map { |field| field[:name] }
+ service.properties.slice(*field_names)
+ end
+ end
+
+ class ProjectHook < ::API::Entities::Hook
+ expose :project_id, :issues_events, :merge_requests_events
+ expose :note_events, :build_events, :pipeline_events, :wiki_page_events
+ end
end
end
end
diff --git a/lib/api/v3/environments.rb b/lib/api/v3/environments.rb
index 3effccfa708..3056b70e6ef 100644
--- a/lib/api/v3/environments.rb
+++ b/lib/api/v3/environments.rb
@@ -1,6 +1,7 @@
module API
module V3
class Environments < Grape::API
+ include ::API::Helpers::CustomValidators
include PaginationParams
before { authenticate! }
@@ -9,9 +10,66 @@ module API
requires :id, type: String, desc: 'The project ID'
end
resource :projects do
+ desc 'Get all environments of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ use :pagination
+ end
+ get ':id/environments' do
+ authorize! :read_environment, user_project
+
+ present paginate(user_project.environments), with: Entities::Environment
+ end
+
+ desc 'Creates a new environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the environment to be created'
+ optional :external_url, type: String, desc: 'URL on which this deployment is viewable'
+ optional :slug, absence: { message: "is automatically generated and cannot be changed" }
+ end
+ post ':id/environments' do
+ authorize! :create_environment, user_project
+
+ environment = user_project.environments.create(declared_params)
+
+ if environment.persisted?
+ present environment, with: Entities::Environment
+ else
+ render_validation_error!(environment)
+ end
+ end
+
+ desc 'Updates an existing environment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success Entities::Environment
+ end
+ params do
+ requires :environment_id, type: Integer, desc: 'The environment ID'
+ optional :name, type: String, desc: 'The new environment name'
+ optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable'
+ optional :slug, absence: { message: "is automatically generated and cannot be changed" }
+ end
+ put ':id/environments/:environment_id' do
+ authorize! :update_environment, user_project
+
+ environment = user_project.environments.find(params[:environment_id])
+
+ update_params = declared_params(include_missing: false).extract!(:name, :external_url)
+ if environment.update(update_params)
+ present environment, with: Entities::Environment
+ else
+ render_validation_error!(environment)
+ end
+ end
+
desc 'Deletes an existing environment' do
detail 'This feature was introduced in GitLab 8.11.'
- success ::API::Entities::Environment
+ success Entities::Environment
end
params do
requires :environment_id, type: Integer, desc: 'The environment ID'
@@ -21,7 +79,7 @@ module API
environment = user_project.environments.find(params[:environment_id])
- present environment.destroy, with: ::API::Entities::Environment
+ present environment.destroy, with: Entities::Environment
end
end
end
diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb
index c826bc4fe0b..0aad87a3f58 100644
--- a/lib/api/v3/groups.rb
+++ b/lib/api/v3/groups.rb
@@ -6,13 +6,20 @@ module API
before { authenticate! }
helpers do
+ params :optional_params do
+ optional :description, type: String, desc: 'The description of the group'
+ optional :visibility_level, type: Integer, desc: 'The visibility level of the group'
+ optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group'
+ optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access'
+ end
+
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
end
def present_groups(groups, options = {})
options = options.reverse_merge(
- with: ::API::Entities::Group,
+ with: Entities::Group,
current_user: current_user,
)
@@ -22,8 +29,36 @@ module API
end
resource :groups do
+ desc 'Get a groups list' do
+ success Entities::Group
+ end
+ params do
+ use :statistics_params
+ optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list'
+ optional :all_available, type: Boolean, desc: 'Show all group that you have access to'
+ optional :search, type: String, desc: 'Search for a specific group'
+ optional :order_by, type: String, values: %w[name path], default: 'name', desc: 'Order by name or path'
+ optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
+ use :pagination
+ end
+ get do
+ groups = if current_user.admin
+ Group.all
+ elsif params[:all_available]
+ GroupsFinder.new.execute(current_user)
+ else
+ current_user.groups
+ end
+
+ groups = groups.search(params[:search]) if params[:search].present?
+ groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
+ groups = groups.reorder(params[:order_by] => params[:sort])
+
+ present_groups groups, statistics: params[:statistics] && current_user.is_admin?
+ end
+
desc 'Get list of owned groups for authenticated user' do
- success ::API::Entities::Group
+ success Entities::Group
end
params do
use :pagination
@@ -32,6 +67,114 @@ module API
get '/owned' do
present_groups current_user.owned_groups, statistics: params[:statistics]
end
+
+ desc 'Create a group. Available only for users who can create groups.' do
+ success Entities::Group
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the group'
+ requires :path, type: String, desc: 'The path of the group'
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
+ use :optional_params
+ end
+ post do
+ authorize! :create_group
+
+ group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
+
+ if group.persisted?
+ present group, with: Entities::Group, current_user: current_user
+ else
+ render_api_error!("Failed to save group #{group.errors.messages}", 400)
+ end
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups do
+ desc 'Update a group. Available only for users who can administrate groups.' do
+ success Entities::Group
+ end
+ params do
+ optional :name, type: String, desc: 'The name of the group'
+ optional :path, type: String, desc: 'The path of the group'
+ use :optional_params
+ at_least_one_of :name, :path, :description, :visibility_level,
+ :lfs_enabled, :request_access_enabled
+ end
+ put ':id' do
+ group = find_group!(params[:id])
+ authorize! :admin_group, group
+
+ if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
+ present group, with: Entities::GroupDetail, current_user: current_user
+ else
+ render_validation_error!(group)
+ end
+ end
+
+ desc 'Get a single group, with containing projects.' do
+ success Entities::GroupDetail
+ end
+ get ":id" do
+ group = find_group!(params[:id])
+ present group, with: Entities::GroupDetail, current_user: current_user
+ end
+
+ desc 'Remove a group.'
+ delete ":id" do
+ group = find_group!(params[:id])
+ authorize! :admin_group, group
+ present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user
+ end
+
+ desc 'Get a list of projects in this group.' do
+ success Entities::Project
+ end
+ params do
+ optional :archived, type: Boolean, default: false, desc: 'Limit by archived status'
+ optional :visibility, type: String, values: %w[public internal private],
+ desc: 'Limit by visibility'
+ optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria'
+ optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at],
+ default: 'created_at', desc: 'Return projects ordered by field'
+ optional :sort, type: String, values: %w[asc desc], default: 'desc',
+ desc: 'Return projects sorted in ascending and descending order'
+ optional :simple, type: Boolean, default: false,
+ desc: 'Return only the ID, URL, name, and path of each project'
+ optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user'
+ optional :starred, type: Boolean, default: false, desc: 'Limit by starred status'
+
+ use :pagination
+ end
+ get ":id/projects" do
+ group = find_group!(params[:id])
+ projects = GroupProjectsFinder.new(group).execute(current_user)
+ projects = filter_projects(projects)
+ entity = params[:simple] ? ::API::Entities::BasicProjectDetails : Entities::Project
+ present paginate(projects), with: entity, current_user: current_user
+ end
+
+ desc 'Transfer a project to the group namespace. Available only for admin.' do
+ success Entities::GroupDetail
+ end
+ params do
+ requires :project_id, type: String, desc: 'The ID or path of the project'
+ end
+ post ":id/projects/:project_id" do
+ authenticated_as_admin!
+ group = find_group!(params[:id])
+ project = find_project!(params[:project_id])
+ result = ::Projects::TransferService.new(project, current_user).execute(group)
+
+ if result
+ present group, with: Entities::GroupDetail, current_user: current_user
+ else
+ render_api_error!("Failed to transfer project #{project.errors.messages}", 400)
+ end
+ end
end
end
end
diff --git a/lib/api/v3/helpers.rb b/lib/api/v3/helpers.rb
new file mode 100644
index 00000000000..0f234d4cdad
--- /dev/null
+++ b/lib/api/v3/helpers.rb
@@ -0,0 +1,19 @@
+module API
+ module V3
+ module Helpers
+ def find_project_issue(id)
+ IssuesFinder.new(current_user, project_id: user_project.id).find(id)
+ end
+
+ def find_project_merge_request(id)
+ MergeRequestsFinder.new(current_user, project_id: user_project.id).find(id)
+ end
+
+ def find_merge_request_with_access(id, access_level = :read_merge_request)
+ merge_request = user_project.merge_requests.find(id)
+ authorize! access_level, merge_request
+ merge_request
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/merge_request_diffs.rb b/lib/api/v3/merge_request_diffs.rb
new file mode 100644
index 00000000000..a462803e26c
--- /dev/null
+++ b/lib/api/v3/merge_request_diffs.rb
@@ -0,0 +1,43 @@
+module API
+ module V3
+ # MergeRequestDiff API
+ class MergeRequestDiffs < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ desc 'Get a list of merge request diff versions' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success ::API::Entities::MergeRequestDiff
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ end
+
+ get ":id/merge_requests/:merge_request_id/versions" do
+ merge_request = find_merge_request_with_access(params[:merge_request_id])
+
+ present merge_request.merge_request_diffs, with: ::API::Entities::MergeRequestDiff
+ end
+
+ desc 'Get a single merge request diff version' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success ::API::Entities::MergeRequestDiffFull
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
+ end
+
+ get ":id/merge_requests/:merge_request_id/versions/:version_id" do
+ merge_request = find_merge_request_with_access(params[:merge_request_id])
+
+ present merge_request.merge_request_diffs.find(params[:version_id]), with: ::API::Entities::MergeRequestDiffFull
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 654e818e1b5..7dbd4691a94 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -28,6 +28,14 @@ module API
render_api_error!(errors, 400)
end
+ def issue_entity(project)
+ if project.has_external_issue_tracker?
+ ::API::Entities::ExternalIssue
+ else
+ ::API::Entities::Issue
+ end
+ end
+
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
diff --git a/lib/api/v3/milestones.rb b/lib/api/v3/milestones.rb
new file mode 100644
index 00000000000..2a850a08a8a
--- /dev/null
+++ b/lib/api/v3/milestones.rb
@@ -0,0 +1,64 @@
+module API
+ module V3
+ class Milestones < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ helpers do
+ def filter_milestones_state(milestones, state)
+ case state
+ when 'active' then milestones.active
+ when 'closed' then milestones.closed
+ else milestones
+ end
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get a list of project milestones' do
+ success ::API::Entities::Milestone
+ end
+ params do
+ optional :state, type: String, values: %w[active closed all], default: 'all',
+ desc: 'Return "active", "closed", or "all" milestones'
+ optional :iid, type: Array[Integer], desc: 'The IID of the milestone'
+ use :pagination
+ end
+ get ":id/milestones" do
+ authorize! :read_milestone, user_project
+
+ milestones = user_project.milestones
+ milestones = filter_milestones_state(milestones, params[:state])
+ milestones = filter_by_iid(milestones, params[:iid]) if params[:iid].present?
+
+ present paginate(milestones), with: ::API::Entities::Milestone
+ end
+
+ desc 'Get all issues for a single project milestone' do
+ success ::API::Entities::Issue
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/issues' do
+ authorize! :read_milestone, user_project
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_title: milestone.title
+ }
+
+ issues = IssuesFinder.new(current_user, finder_params).execute
+ present paginate(issues), with: ::API::Entities::Issue, current_user: current_user, project: user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/pipelines.rb b/lib/api/v3/pipelines.rb
new file mode 100644
index 00000000000..2c26a5f7d35
--- /dev/null
+++ b/lib/api/v3/pipelines.rb
@@ -0,0 +1,36 @@
+module API
+ module V3
+ class Pipelines < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get all Pipelines of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::Entities::Pipeline
+ end
+ params do
+ use :pagination
+ optional :scope, type: String, values: %w(running branches tags),
+ desc: 'Either running, branches, or tags'
+ end
+ get ':id/pipelines' do
+ authorize! :read_pipeline, user_project
+
+ pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope])
+ present paginate(pipelines), with: ::API::Entities::Pipeline
+ end
+ end
+
+ helpers do
+ def pipeline
+ @pipeline ||= user_project.pipelines.find(params[:pipeline_id])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/project_hooks.rb b/lib/api/v3/project_hooks.rb
new file mode 100644
index 00000000000..861b991b8e1
--- /dev/null
+++ b/lib/api/v3/project_hooks.rb
@@ -0,0 +1,106 @@
+module API
+ module V3
+ class ProjectHooks < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize_admin_project }
+
+ helpers do
+ params :project_hook_properties do
+ requires :url, type: String, desc: "The URL to send the request to"
+ optional :push_events, type: Boolean, desc: "Trigger hook on push events"
+ optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
+ optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
+ optional :build_events, type: Boolean, desc: "Trigger hook on build events"
+ optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
+ optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
+ optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
+ optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get project hooks' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ use :pagination
+ end
+ get ":id/hooks" do
+ hooks = paginate user_project.hooks
+
+ present hooks, with: ::API::V3::Entities::ProjectHook
+ end
+
+ desc 'Get a project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of a project hook'
+ end
+ get ":id/hooks/:hook_id" do
+ hook = user_project.hooks.find(params[:hook_id])
+ present hook, with: ::API::V3::Entities::ProjectHook
+ end
+
+ desc 'Add hook to project' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ use :project_hook_properties
+ end
+ post ":id/hooks" do
+ hook = user_project.hooks.new(declared_params(include_missing: false))
+
+ if hook.save
+ present hook, with: ::API::V3::Entities::ProjectHook
+ else
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
+ end
+ end
+
+ desc 'Update an existing project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: "The ID of the hook to update"
+ use :project_hook_properties
+ end
+ put ":id/hooks/:hook_id" do
+ hook = user_project.hooks.find(params.delete(:hook_id))
+
+ if hook.update_attributes(declared_params(include_missing: false))
+ present hook, with: ::API::V3::Entities::ProjectHook
+ else
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
+ end
+ end
+
+ desc 'Deletes project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
+ end
+ delete ":id/hooks/:hook_id" do
+ begin
+ present user_project.hooks.destroy(params[:hook_id]), with: ::API::V3::Entities::ProjectHook
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ not_found!("Error deleting hook #{params[:hook_id]}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index af0a058f69b..d77185ffe5a 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -537,6 +537,23 @@ module API
]
}
+ trigger_services = {
+ 'mattermost-slash-commands' => [
+ {
+ name: :token,
+ type: String,
+ desc: 'The Mattermost token'
+ }
+ ],
+ 'slack-slash-commands' => [
+ {
+ name: :token,
+ type: String,
+ desc: 'The Slack token'
+ }
+ ]
+ }.freeze
+
resource :projects do
before { authenticate! }
before { authorize_admin_project }
@@ -567,6 +584,57 @@ module API
render_api_error!('400 Bad Request', 400)
end
end
+
+ desc 'Get the service settings for project' do
+ success Entities::ProjectService
+ end
+ params do
+ requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+ 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.is_admin?
+ end
+ end
+
+ trigger_services.each do |service_slug, settings|
+ helpers do
+ def chat_command_service(project, service_slug, params)
+ project.services.active.where(template: false).find do |service|
+ service.try(:token) == params[:token] && service.to_param == service_slug.underscore
+ end
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ 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
+
+ service = chat_command_service(project, service_slug, params)
+ result = service.try(:trigger, params)
+
+ if result
+ status result[:status] || 200
+ present result
+ else
+ not_found!('Service')
+ end
+ end
+ end
end
end
end
diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb
new file mode 100644
index 00000000000..748d6b97d4f
--- /dev/null
+++ b/lib/api/v3/settings.rb
@@ -0,0 +1,137 @@
+module API
+ module V3
+ class Settings < Grape::API
+ before { authenticated_as_admin! }
+
+ helpers do
+ def current_settings
+ @current_setting ||=
+ (ApplicationSetting.current || ApplicationSetting.create_from_defaults)
+ end
+ end
+
+ desc 'Get the current application settings' do
+ success Entities::ApplicationSetting
+ end
+ get "application/settings" do
+ present current_settings, with: Entities::ApplicationSetting
+ end
+
+ desc 'Modify application settings' do
+ success Entities::ApplicationSetting
+ end
+ params do
+ optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
+ optional :default_project_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default project visibility'
+ optional :default_snippet_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default snippet visibility'
+ optional :default_group_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default group visibility'
+ optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.'
+ optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project],
+ desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
+ optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources'
+ optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.'
+ optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
+ optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
+ optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
+ optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.'
+ optional :user_oauth_applications, type: Boolean, desc: 'Allow users to register any application to use GitLab as an OAuth provider'
+ optional :user_default_external, type: Boolean, desc: 'Newly registered users will by default be external'
+ optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled'
+ optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up'
+ optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
+ optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups'
+ given domain_blacklist_enabled: ->(val) { val } do
+ requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com'
+ end
+ optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
+ optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled'
+ optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication'
+ given require_two_factor_authentication: ->(val) { val } do
+ requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication'
+ end
+ optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
+ optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out'
+ optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application'
+ optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
+ optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects'
+ given shared_runners_enabled: ->(val) { val } do
+ requires :shared_runners_text, type: String, desc: 'Shared runners text '
+ end
+ optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have"
+ optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
+ optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
+ optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics'
+ given metrics_enabled: ->(val) { val } do
+ requires :metrics_host, type: String, desc: 'The InfluxDB host'
+ requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB'
+ requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open'
+ requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out'
+ requires :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.'
+ requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds'
+ requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet'
+ end
+ optional :sidekiq_throttling_enabled, type: Boolean, desc: 'Enable Sidekiq Job Throttling'
+ given sidekiq_throttling_enabled: ->(val) { val } do
+ requires :sidekiq_throttling_queus, type: Array[String], desc: 'Choose which queues you wish to throttle'
+ requires :sidekiq_throttling_factor, type: Float, desc: 'The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.'
+ end
+ optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts'
+ given recaptcha_enabled: ->(val) { val } do
+ requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha'
+ requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha'
+ end
+ optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues'
+ given akismet_enabled: ->(val) { val } do
+ requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com'
+ end
+ optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.'
+ optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com'
+ given sentry_enabled: ->(val) { val } do
+ requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name'
+ end
+ optional :repository_storage, type: String, desc: 'Storage paths for new projects'
+ optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues."
+ optional :koding_enabled, type: Boolean, desc: 'Enable Koding'
+ given koding_enabled: ->(val) { val } do
+ requires :koding_url, type: String, desc: 'The Koding team URL'
+ end
+ optional :plantuml_enabled, type: Boolean, desc: 'Enable PlantUML'
+ given plantuml_enabled: ->(val) { val } do
+ requires :plantuml_url, type: String, desc: 'The PlantUML server URL'
+ end
+ optional :version_check_enabled, type: Boolean, desc: 'Let GitLab inform you when an update is available.'
+ optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.'
+ optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
+ optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
+ given housekeeping_enabled: ->(val) { val } do
+ requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance."
+ requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run."
+ requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run."
+ requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run."
+ end
+ optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
+ at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility,
+ :default_group_visibility, :restricted_visibility_levels, :import_sources,
+ :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit,
+ :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources,
+ :user_oauth_applications, :user_default_external, :signup_enabled,
+ :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled,
+ :after_sign_up_text, :signin_enabled, :require_two_factor_authentication,
+ :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text,
+ :shared_runners_enabled, :max_artifacts_size, :max_pages_size, :container_registry_token_expire_delay,
+ :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled,
+ :akismet_enabled, :admin_notification_email, :sentry_enabled,
+ :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled,
+ :version_check_enabled, :email_author_in_body, :html_emails_enabled,
+ :housekeeping_enabled, :terminal_max_session_time
+ end
+ put "application/settings" do
+ if current_settings.update_attributes(declared_params(include_missing: false))
+ present current_settings, with: Entities::ApplicationSetting
+ else
+ render_validation_error!(current_settings)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb
new file mode 100644
index 00000000000..07dac7e9904
--- /dev/null
+++ b/lib/api/v3/snippets.rb
@@ -0,0 +1,138 @@
+module API
+ module V3
+ class Snippets < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ resource :snippets do
+ helpers do
+ def snippets_for_current_user
+ SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user)
+ end
+
+ def public_snippets
+ SnippetsFinder.new.execute(current_user, filter: :public)
+ end
+ end
+
+ desc 'Get a snippets list for authenticated user' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ use :pagination
+ end
+ get do
+ present paginate(snippets_for_current_user), with: ::API::Entities::PersonalSnippet
+ end
+
+ desc 'List all public snippets current_user has access to' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ use :pagination
+ end
+ get 'public' do
+ present paginate(public_snippets), with: ::API::Entities::PersonalSnippet
+ end
+
+ desc 'Get a single snippet' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ end
+ get ':id' do
+ snippet = snippets_for_current_user.find(params[:id])
+ present snippet, with: ::API::Entities::PersonalSnippet
+ end
+
+ desc 'Create new snippet' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ requires :title, type: String, desc: 'The title of a snippet'
+ requires :file_name, type: String, desc: 'The name of a snippet file'
+ requires :content, type: String, desc: 'The content of a snippet'
+ optional :visibility_level, type: Integer,
+ values: Gitlab::VisibilityLevel.values,
+ default: Gitlab::VisibilityLevel::INTERNAL,
+ desc: 'The visibility level of the snippet'
+ end
+ post do
+ attrs = declared_params(include_missing: false).merge(request: request, api: true)
+ snippet = CreateSnippetService.new(nil, current_user, attrs).execute
+
+ if snippet.persisted?
+ present snippet, with: ::API::Entities::PersonalSnippet
+ else
+ render_validation_error!(snippet)
+ end
+ end
+
+ desc 'Update an existing snippet' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ optional :title, type: String, desc: 'The title of a snippet'
+ optional :file_name, type: String, desc: 'The name of a snippet file'
+ optional :content, type: String, desc: 'The content of a snippet'
+ optional :visibility_level, type: Integer,
+ values: Gitlab::VisibilityLevel.values,
+ desc: 'The visibility level of the snippet'
+ at_least_one_of :title, :file_name, :content, :visibility_level
+ end
+ put ':id' do
+ snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ return not_found!('Snippet') unless snippet
+ authorize! :update_personal_snippet, snippet
+
+ attrs = declared_params(include_missing: false)
+
+ UpdateSnippetService.new(nil, current_user, snippet, attrs).execute
+ if snippet.persisted?
+ present snippet, with: ::API::Entities::PersonalSnippet
+ else
+ render_validation_error!(snippet)
+ end
+ end
+
+ desc 'Remove snippet' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ success ::API::Entities::PersonalSnippet
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ end
+ delete ':id' do
+ snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ return not_found!('Snippet') unless snippet
+ authorize! :destroy_personal_snippet, snippet
+ snippet.destroy
+ no_content!
+ end
+
+ desc 'Get a raw snippet' do
+ detail 'This feature was introduced in GitLab 8.15.'
+ end
+ params do
+ requires :id, type: Integer, desc: 'The ID of a snippet'
+ end
+ get ":id/raw" do
+ snippet = snippets_for_current_user.find_by(id: params.delete(:id))
+ return not_found!('Snippet') unless snippet
+
+ env['api.format'] = :txt
+ content_type 'text/plain'
+ present snippet.content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/time_tracking_endpoints.rb b/lib/api/v3/time_tracking_endpoints.rb
new file mode 100644
index 00000000000..81ae4e8137d
--- /dev/null
+++ b/lib/api/v3/time_tracking_endpoints.rb
@@ -0,0 +1,116 @@
+module API
+ module V3
+ module TimeTrackingEndpoints
+ extend ActiveSupport::Concern
+
+ included do
+ helpers do
+ def issuable_name
+ declared_params.has_key?(:issue_id) ? 'issue' : 'merge_request'
+ end
+
+ def issuable_key
+ "#{issuable_name}_id".to_sym
+ end
+
+ def update_issuable_key
+ "update_#{issuable_name}".to_sym
+ end
+
+ def read_issuable_key
+ "read_#{issuable_name}".to_sym
+ end
+
+ def load_issuable
+ @issuable ||= begin
+ case issuable_name
+ when 'issue'
+ find_project_issue(params.delete(issuable_key))
+ when 'merge_request'
+ find_project_merge_request(params.delete(issuable_key))
+ end
+ end
+ end
+
+ def update_issuable(attrs)
+ custom_params = declared_params(include_missing: false)
+ custom_params.merge!(attrs)
+
+ issuable = update_service.new(user_project, current_user, custom_params).execute(load_issuable)
+ if issuable.valid?
+ present issuable, with: ::API::Entities::IssuableTimeStats
+ else
+ render_validation_error!(issuable)
+ end
+ end
+
+ def update_service
+ issuable_name == 'issue' ? ::Issues::UpdateService : ::MergeRequests::UpdateService
+ end
+ end
+
+ issuable_name = name.end_with?('Issues') ? 'issue' : 'merge_request'
+ issuable_collection_name = issuable_name.pluralize
+ issuable_key = "#{issuable_name}_id".to_sym
+
+ desc "Set a time estimate for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ requires :duration, type: String, desc: 'The duration to be parsed'
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/time_estimate" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(time_estimate: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)))
+ end
+
+ desc "Reset the time estimate for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_time_estimate" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(time_estimate: 0)
+ end
+
+ desc "Add spent time for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ requires :duration, type: String, desc: 'The duration to be parsed'
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do
+ authorize! update_issuable_key, load_issuable
+
+ update_issuable(spend_time: {
+ duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)),
+ user: current_user
+ })
+ end
+
+ desc "Reset spent time for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ post ":id/#{issuable_collection_name}/:#{issuable_key}/reset_spent_time" do
+ authorize! update_issuable_key, load_issuable
+
+ status :ok
+ update_issuable(spend_time: { duration: :reset, user: current_user })
+ end
+
+ desc "Show time stats for a project #{issuable_name}"
+ params do
+ requires issuable_key, type: Integer, desc: "The ID of a project #{issuable_name}"
+ end
+ get ":id/#{issuable_collection_name}/:#{issuable_key}/time_stats" do
+ authorize! read_issuable_key, load_issuable
+
+ present load_issuable, with: ::API::Entities::IssuableTimeStats
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 4051d4bca8d..1dfdb6a5956 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -7,8 +7,81 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
+ desc 'Trigger a GitLab project build' do
+ success ::API::V3::Entities::TriggerRequest
+ end
+ params do
+ requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
+ requires :token, type: String, desc: 'The unique token of trigger'
+ optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
+ end
+ post ":id/(ref/:ref/)trigger/builds" do
+ project = find_project(params[:id])
+ trigger = Ci::Trigger.find_by_token(params[:token].to_s)
+ not_found! unless project && trigger
+ unauthorized! unless trigger.project == project
+
+ # validate variables
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
+ end
+
+ # create request and trigger builds
+ trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
+ if trigger_request
+ present trigger_request, with: ::API::V3::Entities::TriggerRequest
+ else
+ errors = 'No builds created'
+ render_api_error!(errors, 400)
+ end
+ end
+
+ desc 'Get triggers list' do
+ success ::API::V3::Entities::Trigger
+ end
+ params do
+ use :pagination
+ end
+ get ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ triggers = user_project.triggers.includes(:trigger_requests)
+
+ present paginate(triggers), with: ::API::V3::Entities::Trigger
+ end
+
+ desc 'Get specific trigger of a project' do
+ success ::API::V3::Entities::Trigger
+ end
+ params do
+ requires :token, type: String, desc: 'The unique token of trigger'
+ end
+ get ':id/triggers/:token' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ return not_found!('Trigger') unless trigger
+
+ present trigger, with: ::API::V3::Entities::Trigger
+ end
+
+ desc 'Create a trigger' do
+ success ::API::V3::Entities::Trigger
+ end
+ post ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.create
+
+ present trigger, with: ::API::V3::Entities::Trigger
+ end
+
desc 'Delete a trigger' do
- success ::API::Entities::Trigger
+ success ::API::V3::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
@@ -22,7 +95,7 @@ module API
trigger.destroy
- present trigger, with: ::API::Entities::Trigger
+ present trigger, with: ::API::V3::Entities::Trigger
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 5cc164a6325..7b4476fa4db 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -51,7 +51,8 @@ module Backup
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
- encryption: Gitlab.config.backup.upload.encryption)
+ encryption: Gitlab.config.backup.upload.encryption,
+ storage_class: Gitlab.config.backup.upload.storage_class)
$progress.puts "done".color(:green)
else
puts "uploading backup to #{remote_directory} failed".color(:red)
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index d16d5ba4960..3c4ba5d50e6 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -180,9 +180,8 @@ module Backup
return unless Dir.exist?(path)
dir_entries = Dir.entries(path)
- %w[annex custom_hooks].each do |entry|
- yield(entry) if dir_entries.include?(entry)
- end
+
+ yield('custom_hooks') if dir_entries.include?('custom_hooks')
end
def prepare
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index a8c1ca0c60a..d6138816e70 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -17,8 +17,8 @@ module Banzai
next unless content.include?(':') || node.text.match(emoji_unicode_pattern)
- html = emoji_name_image_filter(content)
- html = emoji_unicode_image_filter(html)
+ html = emoji_unicode_element_unicode_filter(content)
+ html = emoji_name_element_unicode_filter(html)
next if html == content
@@ -27,33 +27,30 @@ module Banzai
doc
end
- # Replace :emoji: with corresponding images.
+ # Replace :emoji: with corresponding gl-emoji unicode.
#
# text - String text to replace :emoji: in.
#
- # Returns a String with :emoji: replaced with images.
- def emoji_name_image_filter(text)
+ # Returns a String with :emoji: replaced with gl-emoji unicode.
+ def emoji_name_element_unicode_filter(text)
text.gsub(emoji_pattern) do |match|
name = $1
- emoji_image_tag(name, emoji_url(name))
+ Gitlab::Emoji.gl_emoji_tag(name)
end
end
- # Replace unicode emoji with corresponding images if they exist.
+ # Replace unicode emoji with corresponding gl-emoji unicode.
#
# text - String text to replace unicode emoji in.
#
- # Returns a String with unicode emoji replaced with images.
- def emoji_unicode_image_filter(text)
+ # Returns a String with unicode emoji replaced with gl-emoji unicode.
+ def emoji_unicode_element_unicode_filter(text)
text.gsub(emoji_unicode_pattern) do |moji|
- emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji))
+ emoji_info = Gitlab::Emoji.emojis_by_moji[moji]
+ Gitlab::Emoji.gl_emoji_tag(emoji_info['name'])
end
end
- def emoji_image_tag(emoji_name, emoji_url)
- "<img class='emoji' title=':#{emoji_name}:' alt=':#{emoji_name}:' src='#{emoji_url}' height='20' width='20' align='absmiddle' />"
- end
-
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
@emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
@@ -66,52 +63,13 @@ module Banzai
private
- def emoji_url(name)
- emoji_path = emoji_filename(name)
-
- if context[:asset_host]
- # Asset host is specified.
- url_to_image(emoji_path)
- elsif context[:asset_root]
- # Gitlab url is specified
- File.join(context[:asset_root], url_to_image(emoji_path))
- else
- # All other cases
- url_to_image(emoji_path)
- end
- end
-
- def emoji_unicode_url(moji)
- emoji_unicode_path = emoji_unicode_filename(moji)
-
- if context[:asset_host]
- url_to_image(emoji_unicode_path)
- elsif context[:asset_root]
- File.join(context[:asset_root], url_to_image(emoji_unicode_path))
- else
- url_to_image(emoji_unicode_path)
- end
- end
-
- def url_to_image(image)
- ActionController::Base.helpers.url_to_image(image)
- end
-
def emoji_pattern
self.class.emoji_pattern
end
- def emoji_filename(name)
- "#{Gitlab::Emoji.emoji_filename(name)}.png"
- end
-
def emoji_unicode_pattern
self.class.emoji_unicode_pattern
end
-
- def emoji_unicode_filename(name)
- "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png"
- end
end
end
end
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index af1e575fc89..d5f9e252f62 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -35,6 +35,10 @@ module Banzai
# Allow span elements
whitelist[:elements].push('span')
+ # Allow html5 details/summary elements
+ whitelist[:elements].push('details')
+ whitelist[:elements].push('summary')
+
# Allow abbr elements with title attribute
whitelist[:elements].push('abbr')
whitelist[:attributes]['abbr'] = %w(title)
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index e390919ae1d..15a461a16dd 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -58,7 +58,7 @@ module Ci
commands: job[:commands],
tag_list: job[:tags] || [],
name: job[:name].to_s,
- allow_failure: job[:allow_failure] || false,
+ allow_failure: job[:ignore],
when: job[:when] || 'on_success',
environment: job[:environment_name],
coverage_regex: job[:coverage],
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 0a5abc92190..0a0bd0e781c 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -23,22 +23,25 @@ module Gitlab
Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
result
end
def find_with_user_password(login, password)
- user = User.by_login(login)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user! do
+ user = User.by_login(login)
- # If no user is found, or it's an LDAP server, try LDAP.
- # LDAP users are only authenticated via LDAP
- if user.nil? || user.ldap_user?
- # Second chance - try LDAP authentication
- return nil unless Gitlab::LDAP::Config.enabled?
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
+ if user.nil? || user.ldap_user?
+ # Second chance - try LDAP authentication
+ return nil unless Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::Authentication.login(login, password)
- else
- user if user.valid_password?(password)
+ Gitlab::LDAP::Authentication.login(login, password)
+ else
+ user if user.valid_password?(password)
+ end
end
end
diff --git a/lib/gitlab/auth/too_many_ips.rb b/lib/gitlab/auth/too_many_ips.rb
new file mode 100644
index 00000000000..ed862791551
--- /dev/null
+++ b/lib/gitlab/auth/too_many_ips.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module Auth
+ class TooManyIps < StandardError
+ attr_reader :user_id, :ip, :unique_ips_count
+
+ def initialize(user_id, ip, unique_ips_count)
+ @user_id = user_id
+ @ip = ip
+ @unique_ips_count = unique_ips_count
+ end
+
+ def message
+ "User #{user_id} from IP: #{ip} tried logging from too many ips: #{unique_ips_count}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb
new file mode 100644
index 00000000000..bf2239ca150
--- /dev/null
+++ b/lib/gitlab/auth/unique_ips_limiter.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module Auth
+ class UniqueIpsLimiter
+ USER_UNIQUE_IPS_PREFIX = 'user_unique_ips'.freeze
+
+ class << self
+ def limit_user_id!(user_id)
+ if config.unique_ips_limit_enabled
+ ip = RequestContext.client_ip
+ unique_ips = update_and_return_ips_count(user_id, ip)
+
+ raise TooManyIps.new(user_id, ip, unique_ips) if unique_ips > config.unique_ips_limit_per_user
+ end
+ end
+
+ def limit_user!(user = nil)
+ user ||= yield if block_given?
+ limit_user_id!(user.id) unless user.nil?
+ user
+ end
+
+ def config
+ Gitlab::CurrentSettings.current_application_settings
+ end
+
+ def update_and_return_ips_count(user_id, ip)
+ time = Time.now.utc.to_i
+ key = "#{USER_UNIQUE_IPS_PREFIX}:#{user_id}"
+
+ Gitlab::Redis.with do |redis|
+ unique_ips_count = nil
+ redis.multi do |r|
+ r.zadd(key, time, ip)
+ r.zremrangebyscore(key, 0, time - config.unique_ips_limit_time_window)
+ unique_ips_count = r.zcard(key)
+ end
+ unique_ips_count.value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/award_emoji.rb b/lib/gitlab/award_emoji.rb
deleted file mode 100644
index 7555326d384..00000000000
--- a/lib/gitlab/award_emoji.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-module Gitlab
- class AwardEmoji
- CATEGORIES = {
- objects: "Objects",
- travel: "Travel",
- symbols: "Symbols",
- nature: "Nature",
- people: "People",
- activity: "Activity",
- flags: "Flags",
- food: "Food"
- }.with_indifferent_access
-
- def self.normalize_emoji_name(name)
- aliases[name] || name
- end
-
- def self.emoji_by_category
- unless @emoji_by_category
- @emoji_by_category = Hash.new { |h, key| h[key] = [] }
-
- emojis.each do |emoji_name, data|
- data["name"] = emoji_name
-
- # Skip Fitzpatrick(tone) modifiers
- next if data["category"] == "modifier"
-
- category = data["category"]
-
- @emoji_by_category[category] << data
- end
-
- @emoji_by_category = @emoji_by_category.sort.to_h
- end
-
- @emoji_by_category
- end
-
- def self.emojis
- @emojis ||=
- begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
- JSON.parse(File.read(json_path))
- end
- end
-
- def self.aliases
- @aliases ||=
- begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
- JSON.parse(File.read(json_path))
- end
- end
-
- # Returns an Array of Emoji names and their asset URLs.
- def self.urls
- @urls ||= begin
- path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
- # Construct the full asset path ourselves because
- # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds
- # of entries since it has to do a lot of extra work (e.g. regexps).
- prefix = Gitlab::Application.config.assets.prefix
- digest = Gitlab::Application.config.assets.digest
- base =
- if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root
- Gitlab::Application.config.relative_url_root
- else
- ''
- end
-
- JSON.parse(File.read(path)).map do |hash|
- fname =
- if digest
- "#{hash['unicode']}-#{hash['digest']}"
- else
- hash['unicode']
- end
-
- { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/config/entry/cache.rb b/lib/gitlab/ci/config/entry/cache.rb
index 066643ccfcc..f074df9c7a1 100644
--- a/lib/gitlab/ci/config/entry/cache.rb
+++ b/lib/gitlab/ci/config/entry/cache.rb
@@ -22,6 +22,12 @@ module Gitlab
entry :paths, Entry::Paths,
description: 'Specify which paths should be cached across builds.'
+
+ helpers :key
+
+ def value
+ super.merge(key: key_value)
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 7f7662f2776..176301bcca1 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -104,6 +104,14 @@ module Gitlab
(before_script_value.to_a + script_value.to_a).join("\n")
end
+ def manual_action?
+ self.when == 'manual'
+ end
+
+ def ignored?
+ allow_failure.nil? ? manual_action? : allow_failure
+ end
+
private
def inherit!(deps)
@@ -135,7 +143,8 @@ module Gitlab
environment_name: environment_defined? ? environment_value[:name] : nil,
coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value,
- after_script: after_script_value }
+ after_script: after_script_value,
+ ignore: ignored? }
end
end
end
diff --git a/lib/gitlab/ci/config/entry/key.rb b/lib/gitlab/ci/config/entry/key.rb
index 0e4c9fe6edc..f27ad0a7759 100644
--- a/lib/gitlab/ci/config/entry/key.rb
+++ b/lib/gitlab/ci/config/entry/key.rb
@@ -11,6 +11,10 @@ module Gitlab
validations do
validates :config, key: true
end
+
+ def self.default
+ 'default'
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/node.rb b/lib/gitlab/ci/config/entry/node.rb
index 55a5447ab51..a6a914d79c1 100644
--- a/lib/gitlab/ci/config/entry/node.rb
+++ b/lib/gitlab/ci/config/entry/node.rb
@@ -70,6 +70,12 @@ module Gitlab
true
end
+ def inspect
+ val = leaf? ? config : descendants
+ unspecified = specified? ? '' : '(unspecified) '
+ "#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
+ end
+
def self.default
end
diff --git a/lib/gitlab/ci/config/entry/undefined.rb b/lib/gitlab/ci/config/entry/undefined.rb
index b33b8238230..1171ac10f22 100644
--- a/lib/gitlab/ci/config/entry/undefined.rb
+++ b/lib/gitlab/ci/config/entry/undefined.rb
@@ -29,6 +29,10 @@ module Gitlab
def relevant?
false
end
+
+ def inspect
+ "#<#{self.class.name}>"
+ end
end
end
end
diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb
index 0f4b7b24cef..3495b8d0448 100644
--- a/lib/gitlab/ci/status/build/play.rb
+++ b/lib/gitlab/ci/status/build/play.rb
@@ -5,22 +5,10 @@ module Gitlab
class Play < SimpleDelegator
include Status::Extended
- def text
- 'manual'
- end
-
def label
'manual play action'
end
- def icon
- 'icon_status_manual'
- end
-
- def group
- 'manual'
- end
-
def has_action?
can?(user, :update_build, subject)
end
diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb
index 90401cad0d2..e8530f2aaae 100644
--- a/lib/gitlab/ci/status/build/stop.rb
+++ b/lib/gitlab/ci/status/build/stop.rb
@@ -5,22 +5,10 @@ module Gitlab
class Stop < SimpleDelegator
include Status::Extended
- def text
- 'manual'
- end
-
def label
'manual stop action'
end
- def icon
- 'icon_status_manual'
- end
-
- def group
- 'manual'
- end
-
def has_action?
can?(user, :update_build, subject)
end
diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb
new file mode 100644
index 00000000000..5f28521901d
--- /dev/null
+++ b/lib/gitlab/ci/status/manual.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module Ci
+ module Status
+ class Manual < Status::Core
+ def text
+ 'manual'
+ end
+
+ def label
+ 'manual action'
+ end
+
+ def icon
+ 'icon_status_manual'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb
index e50e54b6e99..182a30fd74d 100644
--- a/lib/gitlab/data_builder/pipeline.rb
+++ b/lib/gitlab/data_builder/pipeline.rb
@@ -39,7 +39,7 @@ module Gitlab
started_at: build.started_at,
finished_at: build.finished_at,
when: build.when,
- manual: build.manual?,
+ manual: build.action?,
user: build.user.try(:hook_attrs),
runner: build.runner && runner_hook_attrs(build.runner),
artifacts_file: {
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index d160cadc2d0..f3f417c1a63 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -24,7 +24,7 @@ module Gitlab
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
- if Gitlab::Database.postgresql?
+ if postgresql?
order << ' NULLS LAST'
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
@@ -38,7 +38,7 @@ module Gitlab
def self.nulls_first_order(field, direction = 'ASC')
order = "#{field} #{direction}"
- if Gitlab::Database.postgresql?
+ if postgresql?
order << ' NULLS FIRST'
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
@@ -50,7 +50,7 @@ module Gitlab
end
def self.random
- Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()"
+ postgresql? ? "RANDOM()" : "RAND()"
end
def true_value
diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb
index bbbca8acc40..42703545c4f 100644
--- a/lib/gitlab/emoji.rb
+++ b/lib/gitlab/emoji.rb
@@ -1,7 +1,7 @@
module Gitlab
module Emoji
extend self
-
+
def emojis
Gemojione.index.instance_variable_get(:@emoji_by_name)
end
@@ -18,6 +18,10 @@ module Gitlab
emojis.keys
end
+ def emojis_aliases
+ @emoji_aliases ||= JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'aliases.json')))
+ end
+
def emoji_filename(name)
emojis[name]["unicode"]
end
@@ -25,5 +29,42 @@ module Gitlab
def emoji_unicode_filename(moji)
emojis_by_moji[moji]["unicode"]
end
+
+ def emoji_unicode_version(name)
+ @emoji_unicode_versions_by_name ||= JSON.parse(File.read(Rails.root.join('node_modules', 'emoji-unicode-version', 'emoji-unicode-version-map.json')))
+ @emoji_unicode_versions_by_name[name]
+ end
+
+ def normalize_emoji_name(name)
+ emojis_aliases[name] || name
+ end
+
+ def emoji_image_tag(name, src)
+ "<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />"
+ end
+
+ # CSS sprite fallback takes precedence over image fallback
+ def gl_emoji_tag(name, image: false, sprite: false, force_fallback: false)
+ emoji_name = emojis_aliases[name] || name
+ emoji_info = emojis[emoji_name]
+ emoji_fallback_image_source = ActionController::Base.helpers.url_to_image("emoji/#{emoji_info['name']}.png")
+ emoji_fallback_sprite_class = "emoji-#{emoji_name}"
+
+ data = {
+ name: emoji_name,
+ unicode_version: emoji_unicode_version(emoji_name)
+ }
+ data[:fallback_src] = emoji_fallback_image_source if image
+ data[:fallback_sprite_class] = emoji_fallback_sprite_class if sprite
+ ActionController::Base.helpers.content_tag 'gl-emoji',
+ class: ("emoji-icon #{emoji_fallback_sprite_class}" if force_fallback && sprite),
+ data: data do
+ if force_fallback && !sprite
+ emoji_image_tag(emoji_name, emoji_fallback_image_source)
+ else
+ emoji_info['moji']
+ end
+ end
+ end
end
end
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
new file mode 100644
index 00000000000..0f24f9bbfde
--- /dev/null
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -0,0 +1,66 @@
+module Gitlab
+ module EtagCaching
+ class Middleware
+ RESERVED_WORDS = ProjectPathValidator::RESERVED.map { |word| "/#{word}/" }.join('|')
+ ROUTE_REGEXP = Regexp.union(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z)
+ )
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return @app.call(env) unless enabled_for_current_route?(env)
+ Gitlab::Metrics.add_event(:etag_caching_middleware_used)
+
+ etag, cached_value_present = get_etag(env)
+ if_none_match = env['HTTP_IF_NONE_MATCH']
+
+ if if_none_match == etag
+ Gitlab::Metrics.add_event(:etag_caching_cache_hit)
+ [304, { 'ETag' => etag }, ['']]
+ else
+ track_cache_miss(if_none_match, cached_value_present)
+
+ status, headers, body = @app.call(env)
+ headers['ETag'] = etag
+ [status, headers, body]
+ end
+ end
+
+ private
+
+ def enabled_for_current_route?(env)
+ ROUTE_REGEXP.match(env['PATH_INFO'])
+ end
+
+ def get_etag(env)
+ cache_key = env['PATH_INFO']
+ store = Store.new
+ current_value = store.get(cache_key)
+ cached_value_present = current_value.present?
+
+ unless cached_value_present
+ current_value = store.touch(cache_key, only_if_missing: true)
+ end
+
+ [weak_etag_format(current_value), cached_value_present]
+ end
+
+ def weak_etag_format(value)
+ %Q{W/"#{value}"}
+ end
+
+ def track_cache_miss(if_none_match, cached_value_present)
+ if if_none_match.blank?
+ Gitlab::Metrics.add_event(:etag_caching_header_missing)
+ elsif !cached_value_present
+ Gitlab::Metrics.add_event(:etag_caching_key_not_found)
+ else
+ Gitlab::Metrics.add_event(:etag_caching_resource_changed)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
new file mode 100644
index 00000000000..9532e432f78
--- /dev/null
+++ b/lib/gitlab/etag_caching/store.rb
@@ -0,0 +1,32 @@
+module Gitlab
+ module EtagCaching
+ class Store
+ EXPIRY_TIME = 10.minutes
+ REDIS_NAMESPACE = 'etag:'.freeze
+
+ def get(key)
+ Gitlab::Redis.with { |redis| redis.get(redis_key(key)) }
+ end
+
+ def touch(key, only_if_missing: false)
+ etag = generate_etag
+
+ Gitlab::Redis.with do |redis|
+ redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
+ end
+
+ etag
+ end
+
+ private
+
+ def generate_etag
+ SecureRandom.hex
+ end
+
+ def redis_key(key)
+ "#{REDIS_NAMESPACE}#{key}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9c384069661..6c275a8d5de 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -4,16 +4,17 @@ module Gitlab
gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen"
gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
gon.max_file_size = current_application_settings.max_attachment_size
+ gon.asset_host = ActionController::Base.asset_host
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
- gon.award_menu_url = emojis_path
gon.katex_css_url = ActionController::Base.helpers.asset_path('katex.css')
gon.katex_js_url = ActionController::Base.helpers.asset_path('katex.js')
if current_user
gon.current_user_id = current_user.id
gon.current_username = current_user.username
+ gon.current_user_fullname = current_user.name
end
end
end
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
new file mode 100644
index 00000000000..fef536ecb0b
--- /dev/null
+++ b/lib/gitlab/request_context.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ class RequestContext
+ class << self
+ def client_ip
+ RequestStore[:client_ip]
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ req = Rack::Request.new(env)
+
+ RequestStore[:client_ip] = req.ip
+
+ @app.call(env)
+ end
+ end
+end
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index b7f825e8284..823f697f51c 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -1,24 +1,23 @@
+module DeliverNever
+ def deliver_later
+ self
+ end
+end
+
module Gitlab
class Seeder
def self.quiet
mute_mailer
SeedFu.quiet = true
+
yield
+
SeedFu.quiet = false
puts "\nOK".color(:green)
end
- def self.by_user(user)
- yield
- end
-
def self.mute_mailer
- code = <<-eos
-def Notify.deliver_later
- self
-end
- eos
- eval(code) # rubocop:disable Security/Eval
+ ActionMailer::MessageDelivery.prepend(DeliverNever)
end
end
end
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index aadc401ff8d..11e5f1b645c 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -44,19 +44,42 @@ module Gitlab
# Returns true if all the given job have been completed.
#
- # jids - The Sidekiq job IDs to check.
+ # job_ids - The Sidekiq job IDs to check.
#
# Returns true or false.
- def self.all_completed?(jids)
- keys = jids.map { |jid| key_for(jid) }
+ def self.all_completed?(job_ids)
+ self.num_running(job_ids).zero?
+ end
+
+ # Returns the number of jobs that are running.
+ #
+ # job_ids - The Sidekiq job IDs to check.
+ def self.num_running(job_ids)
+ responses = self.job_status(job_ids)
- responses = Sidekiq.redis do |redis|
+ responses.select(&:present?).count
+ end
+
+ # Returns the number of jobs that have completed.
+ #
+ # job_ids - The Sidekiq job IDs to check.
+ def self.num_completed(job_ids)
+ job_ids.size - self.num_running(job_ids)
+ end
+
+ # Returns the job status for each of the given job IDs.
+ #
+ # job_ids - The Sidekiq job IDs to check.
+ #
+ # Returns an array of true or false indicating job completion.
+ def self.job_status(job_ids)
+ keys = job_ids.map { |jid| key_for(jid) }
+
+ Sidekiq.redis do |redis|
redis.pipelined do
keys.each { |key| redis.exists(key) }
end
end
-
- responses.all? { |value| !value }
end
def self.key_for(jid)
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index b28708c34e1..2248763c106 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -35,6 +35,10 @@ module Gitlab
class << self
delegate :values, to: :options
+ def string_values
+ string_options.keys
+ end
+
def options
{
'Private' => PRIVATE,
@@ -43,6 +47,14 @@ module Gitlab
}
end
+ def string_options
+ {
+ 'private' => PRIVATE,
+ 'internal' => INTERNAL,
+ 'public' => PUBLIC
+ }
+ end
+
def highest_allowed_level
restricted_levels = current_application_settings.restricted_visibility_levels
@@ -82,18 +94,39 @@ module Gitlab
level_name
end
+
+ def level_value(level)
+ return string_options[level] if level.is_a? String
+ level
+ end
+
+ def string_level(level)
+ string_options.key(level)
+ end
end
def private?
- visibility_level_field == PRIVATE
+ visibility_level_value == PRIVATE
end
def internal?
- visibility_level_field == INTERNAL
+ visibility_level_value == INTERNAL
end
def public?
- visibility_level_field == PUBLIC
+ visibility_level_value == PUBLIC
+ end
+
+ def visibility_level_value
+ self[visibility_level_field]
+ end
+
+ def visibility
+ Gitlab::VisibilityLevel.string_level(visibility_level_value)
+ end
+
+ def visibility=(level)
+ self[visibility_level_field] = Gitlab::VisibilityLevel.level_value(level)
end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 3ff9f9eb5e7..eae1a0abf06 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -8,6 +8,7 @@ module Gitlab
VERSION_FILE = 'GITLAB_WORKHORSE_VERSION'.freeze
INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze
+ NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze
# Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32
# bytes https://tools.ietf.org/html/rfc4868#section-2.6
@@ -154,6 +155,18 @@ module Gitlab
Rails.root.join('.gitlab_workhorse_secret')
end
+ def set_key_and_notify(key, value, expire: nil, overwrite: true)
+ Gitlab::Redis.with do |redis|
+ result = redis.set(key, value, ex: expire, nx: !overwrite)
+ if result
+ redis.publish(NOTIFICATION_CHANNEL, "#{key}=#{value}")
+ value
+ else
+ redis.get(key)
+ end
+ end
+ end
+
protected
def encode(hash)
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index ad6df246091..3d60618006c 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -26,7 +26,7 @@ module Mattermost
def session_get(path, options = {})
with_session do |session|
- get(session, path, options)
+ get(session, path, options)
end
end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 5388966605d..688a79c0441 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -153,7 +153,7 @@ module Mattermost
yield
rescue HTTParty::Error => e
raise Mattermost::ConnectionError.new(e.message)
- rescue Errno::ECONNREFUSED
+ rescue Errno::ECONNREFUSED => e
raise Mattermost::ConnectionError.new(e.message)
end
end
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index 09dfd082b3a..2cdbbdece16 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -1,7 +1,18 @@
module Mattermost
class Team < Client
+ # Returns **all** teams for an admin
def all
- session_get('/api/v3/teams/all')
+ session_get('/api/v3/teams/all').values
+ end
+
+ # Creates a team on the linked Mattermost instance, the team admin will be the
+ # `current_user` passed to the Mattermost::Client instance
+ def create(name:, display_name:, type:)
+ session_post('/api/v3/teams/create', body: {
+ name: name,
+ display_name: display_name,
+ type: type
+ }.to_json)
end
end
end
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 5661394058d..330031aaddc 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -82,6 +82,9 @@ server {
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
+ ## [Optional] Enable HTTP Strict Transport Security
+ # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
+
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index 993112aee3b..1f93b5a4dd2 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -5,29 +5,29 @@ namespace :gemojione do
require 'json'
dir = Gemojione.images_path
- digests = []
- aliases = Hash.new { |hash, key| hash[key] = [] }
- aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
-
- JSON.parse(File.read(aliases_path)).each do |alias_name, real_name|
- aliases[real_name] << alias_name
- end
-
- Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
- fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
- digest = Digest::SHA256.file(fpath).hexdigest
-
- digests << { name: name, unicode: emoji_hash['unicode'], digest: digest }
+ resultant_emoji_map = {}
+
+ Gitlab::Emoji.emojis.each do |name, emoji_hash|
+ # Ignore aliases
+ unless Gitlab::Emoji.emojis_aliases.key?(name)
+ fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
+ hash_digest = Digest::SHA256.file(fpath).hexdigest
+
+ entry = {
+ category: emoji_hash['category'],
+ moji: emoji_hash['moji'],
+ unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name),
+ digest: hash_digest,
+ }
- aliases[name].each do |alias_name|
- digests << { name: alias_name, unicode: emoji_hash['unicode'], digest: digest }
+ resultant_emoji_map[name] = entry
end
end
out = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
File.open(out, 'w') do |handle|
- handle.write(JSON.pretty_generate(digests))
+ handle.write(JSON.pretty_generate(resultant_emoji_map))
end
end
@@ -55,21 +55,40 @@ namespace :gemojione do
SPRITESHEET_WIDTH = 860
SPRITESHEET_HEIGHT = 840
+ # Setup a map to rename image files
+ emoji_unicode_string_to_name_map = {}
+ Gitlab::Emoji.emojis.each do |name, emoji_hash|
+ # Ignore aliases
+ unless Gitlab::Emoji.emojis_aliases.key?(name)
+ emoji_unicode_string_to_name_map[emoji_hash['unicode']] = name
+ end
+ end
+
+ # Copy the Gemojione assets to the temporary folder for renaming
+ emoji_dir = "app/assets/images/emoji"
+ FileUtils.rm_rf(emoji_dir)
+ FileUtils.mkdir_p(emoji_dir, mode: 0700)
+ FileUtils.cp_r(File.join(Gemojione.images_path, '.'), emoji_dir)
+ Dir[File.join(emoji_dir, "**/*.png")].each do |png|
+ image_path = png
+ rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
+ end
+
Dir.mktmpdir do |tmpdir|
- # Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.images_path, tmpdir)
+ FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
- resize!(File.join(tmpdir, png), SIZE)
+ tmp_image_path = File.join(tmpdir, png)
+ resize!(tmp_image_path, SIZE)
end
end
- style_path = Rails.root.join(*%w(app assets stylesheets pages emojis.scss))
+ style_path = Rails.root.join(*%w(app assets stylesheets framework emoji-sprites.scss))
# Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')"
- SpriteFactory.run!(File.join(tmpdir, 'png'), {
+ SpriteFactory.run!(tmpdir, {
output_style: style_path,
output_image: "app/assets/images/emoji.png",
selector: '.emoji-',
@@ -83,7 +102,7 @@ namespace :gemojione do
# let's simplify it
system(%Q(sed -i '' "s/width: #{SIZE}px; height: #{SIZE}px; background: image-url('emoji.png')/background-position:/" #{style_path}))
system(%Q(sed -i '' "s/ no-repeat//" #{style_path}))
- system(%Q(sed -i '' "s/ 0px/ 0/" #{style_path}))
+ system(%Q(sed -i '' "s/ 0px/ 0/g" #{style_path}))
# Append a generic rule that applies to all Emojis
File.open(style_path, 'a') do |f|
@@ -92,6 +111,8 @@ namespace :gemojione do
.emoji-icon {
background-image: image-url('emoji.png');
background-repeat: no-repeat;
+ color: transparent;
+ text-indent: -99em;
height: #{SIZE}px;
width: #{SIZE}px;
@@ -112,16 +133,17 @@ namespace :gemojione do
# Now do it again but for Retina
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.images_path, tmpdir)
+ FileUtils.cp_r(File.join(emoji_dir, '.'), tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
- resize!(File.join(tmpdir, png), RETINA)
+ tmp_image_path = File.join(tmpdir, png)
+ resize!(tmp_image_path, RETINA)
end
end
# Combine the resized assets into a packed sprite and re-generate the SCSS
- SpriteFactory.run!(File.join(tmpdir), {
+ SpriteFactory.run!(tmpdir, {
output_image: "app/assets/images/emoji@2x.png",
style: false,
nocomments: true,
@@ -155,4 +177,20 @@ namespace :gemojione do
image.write(image_path) { self.quality = 100 }
image.destroy!
end
+
+ EMOJI_IMAGE_PATH_RE = /(.*?)(([0-9a-f]-?)+)\.png$/i
+ def rename_to_named_emoji_image!(emoji_unicode_string_to_name_map, image_path)
+ # Rename file from unicode to emoji name
+ matches = EMOJI_IMAGE_PATH_RE.match(image_path)
+ preceding_path = matches[1]
+ unicode_string = matches[2]
+ name = emoji_unicode_string_to_name_map[unicode_string]
+ if name
+ new_png_path = File.join(preceding_path, "#{name}.png")
+ FileUtils.mv(image_path, new_png_path)
+ new_png_path
+ else
+ puts "Warning: emoji_unicode_string_to_name_map missing entry for #{unicode_string}. Full path: #{image_path}"
+ end
+ end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index ae78fe64eb8..b8dd654b9a9 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -14,6 +14,8 @@ namespace :gitlab do
rake_version = run_and_match(%w(rake --version), /[\d\.]+/).try(:to_s)
# check redis version
redis_version = run_and_match(%w(redis-cli --version), /redis-cli (\d+\.\d+\.\d+)/).to_a
+ # check Git version
+ git_version = run_and_match([Gitlab.config.git.bin_path, '--version'], /git version ([\d\.]+)/).to_a
puts ""
puts "System information".color(:yellow)
@@ -26,6 +28,7 @@ namespace :gitlab do
puts "Bundler Version:#{bunder_version || "unknown".color(:red)}"
puts "Rake Version:\t#{rake_version || "unknown".color(:red)}"
puts "Redis Version:\t#{redis_version[1] || "unknown".color(:red)}"
+ puts "Git Version:\t#{git_version[1] || "unknown".color(:red)}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
# check database adapter