diff options
Diffstat (limited to 'lib/api')
-rw-r--r-- | lib/api/api.rb | 4 | ||||
-rw-r--r-- | lib/api/commit_statuses.rb | 2 | ||||
-rw-r--r-- | lib/api/entities.rb | 74 | ||||
-rw-r--r-- | lib/api/files.rb | 6 | ||||
-rw-r--r-- | lib/api/groups.rb | 12 | ||||
-rw-r--r-- | lib/api/helpers.rb | 57 | ||||
-rw-r--r-- | lib/api/merge_requests.rb | 80 | ||||
-rw-r--r-- | lib/api/project_hooks.rb | 2 | ||||
-rw-r--r-- | lib/api/projects.rb | 37 | ||||
-rw-r--r-- | lib/api/repositories.rb | 35 | ||||
-rw-r--r-- | lib/api/tags.rb | 86 | ||||
-rw-r--r-- | lib/api/triggers.rb | 48 | ||||
-rw-r--r-- | lib/api/users.rb | 16 |
13 files changed, 350 insertions, 109 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index afc0402f9e1..7834262d612 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -25,7 +25,7 @@ module API format :json content_type :txt, "text/plain" - helpers APIHelpers + helpers Helpers mount Groups mount GroupMembers @@ -52,5 +52,7 @@ module API mount Labels mount Settings mount Keys + mount Tags + mount Triggers end end diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 2c0596c9dfb..1162271f5fc 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -53,7 +53,7 @@ module API name = params[:name] || params[:context] status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref]) - status ||= GenericCommitStatus.new(commit: ci_commit, user: current_user) + status ||= GenericCommitStatus.new(project: @project, commit: ci_commit, user: current_user) status.update(attrs) case params[:state].to_s diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 883a5e14b17..26e7c956e8f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -45,7 +45,8 @@ module API class ProjectHook < Hook expose :project_id, :push_events - expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :enable_ssl_verification + expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events + expose :enable_ssl_verification end class ForkedFromProject < Grape::Entity @@ -62,12 +63,14 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :shared_runners_enabled expose :creator_id expose :namespace - expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } + expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ |project, options| project.forked? } expose :avatar_url expose :star_count, :forks_count + expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } end class ProjectMember < UserBasic @@ -95,25 +98,6 @@ module API end end - class RepoTag < Grape::Entity - expose :name - expose :message do |repo_obj, _options| - if repo_obj.respond_to?(:message) - repo_obj.message - else - nil - end - end - - expose :commit do |repo_obj, options| - if repo_obj.respond_to?(:commit) - repo_obj.commit - elsif options[:project] - options[:project].repository.commit(repo_obj.target) - end - end - end - class RepoObject < Grape::Entity expose :name @@ -181,13 +165,15 @@ module API end class MergeRequest < ProjectEntity - expose :target_branch, :source_branch, :upvotes, :downvotes + expose :target_branch, :source_branch + expose :upvotes, :downvotes expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels expose :description expose :work_in_progress?, as: :work_in_progress expose :milestone, using: Entities::Milestone + expose :merge_when_build_succeeds end class MergeRequestChanges < MergeRequest @@ -211,6 +197,8 @@ module API expose :author, using: Entities::UserBasic expose :created_at expose :system?, as: :system + expose :noteable_id, :noteable_type + # upvote? and downvote? are deprecated, always return false expose :upvote?, as: :upvote expose :downvote?, as: :downvote end @@ -231,7 +219,7 @@ module API class CommitStatus < Grape::Entity expose :id, :sha, :ref, :status, :name, :target_url, :description, - :created_at, :started_at, :finished_at + :created_at, :started_at, :finished_at, :allow_failure expose :author, using: Entities::UserBasic end @@ -240,6 +228,8 @@ module API expose :target_id, :target_type, :author_id expose :data, :target_title expose :created_at + expose :note, using: Entities::Note, if: ->(event, options) { event.note? } + expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author } expose :author_username do |event, options| if event.author @@ -264,7 +254,7 @@ module API class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active - expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events + expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events # Expose serialized properties expose :properties do |service, options| field_names = service.fields. @@ -341,5 +331,39 @@ module API expose :user_oauth_applications expose :after_sign_out_path end + + class Release < Grape::Entity + expose :tag, as: :tag_name + expose :description + end + + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + + expose :release, using: Entities::Release do |repo_obj, options| + if options[:project] + options[:project].releases.find_by(tag: repo_obj.name) + end + end + end + + class TriggerRequest < Grape::Entity + expose :id, :variables + end end end diff --git a/lib/api/files.rb b/lib/api/files.rb index 308c84dd135..8ad2c1883c7 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -7,7 +7,7 @@ module API def commit_params(attrs) { file_path: attrs[:file_path], - current_branch: attrs[:branch_name], + source_branch: attrs[:branch_name], target_branch: attrs[:branch_name], commit_message: attrs[:commit_message], file_content: attrs[:content], @@ -43,7 +43,8 @@ module API # "content": "IyA9PSBTY2hlbWEgSW5mb3...", # "ref": "master", # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50" + # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", + # "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", # } # get ":id/repository/files" do @@ -71,6 +72,7 @@ module API ref: ref, blob_id: blob.id, commit_id: commit.id, + last_commit_id: user_project.repository.last_commit_for_path(commit.sha, file_path).id } else not_found! 'File' diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 024aeec2e14..1a14d870a4a 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -65,6 +65,18 @@ module API DestroyGroupService.new(group, current_user).execute end + # Get a list of projects in this group + # + # Example Request: + # GET /groups/:id/projects + get ":id/projects" do + group = find_group(params[:id]) + projects = group.projects + projects = filter_projects(projects) + projects = paginate projects + present projects, with: Entities::Project + end + # Transfer a project to the Group namespace # # Parameters: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 549b1f9e9a7..a4df810e755 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -1,5 +1,5 @@ module API - module APIHelpers + module Helpers PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" PRIVATE_TOKEN_PARAM = :private_token SUDO_HEADER ="HTTP_SUDO" @@ -133,6 +133,12 @@ module API authorize! :admin_project, user_project end + def require_gitlab_workhorse! + unless env['HTTP_GITLAB_WORKHORSE'].present? + forbidden!('Request should be executed via GitLab Workhorse') + end + end + def can?(object, action, subject) abilities.allowed?(object, action, subject) end @@ -234,6 +240,10 @@ module API render_api_error!(message || '409 Conflict', 409) end + def file_to_large! + render_api_error!('413 Request Entity Too Large', 413) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) @@ -256,12 +266,7 @@ module API projects = projects.search(params[:search]) end - if params[:ci_enabled_first].present? - projects.includes(:gitlab_ci_service). - reorder("services.active DESC, projects.#{project_order_by} #{project_sort}") - else - projects.reorder(project_order_by => project_sort) - end + projects.reorder(project_order_by => project_sort) end def project_order_by @@ -282,6 +287,44 @@ module API end end + # file helpers + + def uploaded_file!(field, uploads_path) + if params[field] + bad_request!("#{field} is not a file") unless params[field].respond_to?(:filename) + return params[field] + end + + # sanitize file paths + # this requires all paths to exist + required_attributes! %W(#{field}.path) + uploads_path = File.realpath(uploads_path) + file_path = File.realpath(params["#{field}.path"]) + bad_request!('Bad file path') unless file_path.start_with?(uploads_path) + + UploadedFile.new( + file_path, + params["#{field}.name"], + params["#{field}.type"] || 'application/octet-stream', + ) + end + + def present_file!(path, filename, content_type = 'application/octet-stream') + filename ||= File.basename(path) + header['Content-Disposition'] = "attachment; filename=#{filename}" + header['Content-Transfer-Encoding'] = 'binary' + content_type content_type + + # Support download acceleration + case headers['X-Sendfile-Type'] + when 'X-Sendfile' + header['X-Sendfile'] = path + body + else + file FileStreamer.new(path) + end + end + private def add_pagination_headers(paginated, per_page) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 6eb84baf9cb..3c1c6bda260 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -76,6 +76,22 @@ module API present merge_request, with: Entities::MergeRequest end + # Show MR commits + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of MR + # + # Example: + # GET /projects/:id/merge_request/:merge_request_id/commits + # + get ':id/merge_request/:merge_request_id/commits' do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + authorize! :read_merge_request, merge_request + present merge_request.commits, with: Entities::RepoCommit + end + # Show MR changes # # Parameters: @@ -179,46 +195,54 @@ module API # Merge MR # # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - ID of MR - # merge_commit_message (optional) - Custom merge commit message + # id (required) - The ID of a project + # merge_request_id (required) - ID of MR + # merge_commit_message (optional) - Custom merge commit message + # should_remove_source_branch (optional) - When true, the source branch will be deleted if possible + # merge_when_build_succeeds (optional) - When true, this MR will be merged when the build succeeds # Example: # PUT /projects/:id/merge_request/:merge_request_id/merge # put ":id/merge_request/:merge_request_id/merge" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - allowed = ::Gitlab::GitAccess.new(current_user, user_project). - can_push_to_branch?(merge_request.target_branch) + # Merge request can not be merged + # because user dont have permissions to push into target branch + unauthorized! unless merge_request.can_be_merged_by?(current_user) + not_allowed! if !merge_request.open? || merge_request.work_in_progress? - if allowed - if merge_request.unchecked? - merge_request.check_if_can_be_merged - end + merge_request.check_if_can_be_merged if merge_request.unchecked? - if merge_request.open? && !merge_request.work_in_progress? - if merge_request.can_be_merged? - commit_message = params[:merge_commit_message] || merge_request.merge_commit_message - - ::MergeRequests::MergeService.new(merge_request.target_project, current_user). - execute(merge_request, commit_message) - - present merge_request, with: Entities::MergeRequest - else - render_api_error!('Branch cannot be merged', 405) - end - else - # Merge request can not be merged - # because it is already closed/merged or marked as WIP - not_allowed! - end + render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged? + + merge_params = { + commit_message: params[:merge_commit_message], + should_remove_source_branch: params[:should_remove_source_branch] + } + + if parse_boolean(params[:merge_when_build_succeeds]) && merge_request.ci_commit && merge_request.ci_commit.active? + ::MergeRequests::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request) else - # Merge request can not be merged - # because user dont have permissions to push into target branch - unauthorized! + ::MergeRequests::MergeService.new(merge_request.target_project, current_user, merge_params). + execute(merge_request) end + + present merge_request, with: Entities::MergeRequest end + # Cancel Merge if Merge When build succeeds is enabled + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - ID of MR + # + post ":id/merge_request/:merge_request_id/cancel_merge_when_build_succeeds" do + merge_request = user_project.merge_requests.find(params[:merge_request_id]) + + unauthorized! unless merge_request.can_cancel_merge_when_build_succeeds?(current_user) + + ::MergeRequest::MergeWhenBuildSucceedsService.new(merge_request.target_project, current_user).cancel(merge_request) + end # Get a merge request's comments # diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 882d1a083ad..cf9938d25a7 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -45,6 +45,7 @@ module API :merge_requests_events, :tag_push_events, :note_events, + :build_events, :enable_ssl_verification ] @hook = user_project.hooks.new(attrs) @@ -77,6 +78,7 @@ module API :merge_requests_events, :tag_push_events, :note_events, + :build_events, :enable_ssl_verification ] diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 67ee66a2058..0781236cf6d 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -3,12 +3,16 @@ module API class Projects < Grape::API before { authenticate! } - resource :projects do + resource :projects, requirements: { id: /[^\/]+/ } do helpers do def map_public_to_visibility_level(attrs) publik = attrs.delete(:public) - publik = parse_boolean(publik) - attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true + if publik.present? && !attrs[:visibility_level].present? + publik = parse_boolean(publik) + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE + end attrs end end @@ -21,7 +25,7 @@ module API @projects = current_user.authorized_projects @projects = filter_projects(@projects) @projects = paginate @projects - present @projects, with: Entities::Project + present @projects, with: Entities::ProjectWithAccess, user: current_user end # Get an owned projects list for authenticated user @@ -32,6 +36,17 @@ module API @projects = current_user.owned_projects @projects = filter_projects(@projects) @projects = paginate @projects + present @projects, with: Entities::ProjectWithAccess, user: current_user + end + + # Gets starred project for the authenticated user + # + # Example Request: + # GET /projects/starred + get '/starred' do + @projects = current_user.starred_projects + @projects = filter_projects(@projects) + @projects = paginate @projects present @projects, with: Entities::Project end @@ -44,7 +59,7 @@ module API @projects = Project.all @projects = filter_projects(@projects) @projects = paginate @projects - present @projects, with: Entities::Project + present @projects, with: Entities::ProjectWithAccess, user: current_user end # Get a single project @@ -75,8 +90,10 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # shared_runners_enabled (optional) # namespace_id (optional) - defaults to user namespace # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - 0 by default @@ -90,8 +107,10 @@ module API :description, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, + :shared_runners_enabled, :namespace_id, :public, :visibility_level, @@ -117,8 +136,10 @@ module API # default_branch (optional) - 'master' by default # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # shared_runners_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) # import_url (optional) @@ -132,8 +153,10 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, + :shared_runners_enabled, :public, :visibility_level, :import_url] @@ -172,8 +195,10 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) + # shared_runners_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - visibility level of a project # Example Request @@ -185,8 +210,10 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, + :shared_runners_enabled, :public, :visibility_level] attrs = map_public_to_visibility_level(attrs) diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 20d568cf462..d7c48639eba 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -16,41 +16,6 @@ module API end end - # Get a project repository tags - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/tags - get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, - with: Entities::RepoTag, project: user_project - end - - # Create tag - # - # Parameters: - # id (required) - The ID of a project - # tag_name (required) - The name of the tag - # ref (required) - Create tag from commit sha or branch - # message (optional) - Specifying a message creates an annotated tag. - # Example Request: - # POST /projects/:id/repository/tags - post ':id/repository/tags' do - authorize_push_project - message = params[:message] || nil - result = CreateTagService.new(user_project, current_user). - execute(params[:tag_name], params[:ref], message) - - if result[:status] == :success - present result[:tag], - with: Entities::RepoTag, - project: user_project - else - render_api_error!(result[:message], 400) - end - end - # Get a project repository tree # # Parameters: diff --git a/lib/api/tags.rb b/lib/api/tags.rb new file mode 100644 index 00000000000..47621f443e6 --- /dev/null +++ b/lib/api/tags.rb @@ -0,0 +1,86 @@ +module API + # Git Tags API + class Tags < Grape::API + before { authenticate! } + before { authorize! :download_code, user_project } + + resource :projects do + # Get a project repository tags + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/tags + get ":id/repository/tags" do + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project + end + + # Create tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # ref (required) - Create tag from commit sha or branch + # message (optional) - Specifying a message creates an annotated tag. + # Example Request: + # POST /projects/:id/repository/tags + post ':id/repository/tags' do + authorize_push_project + message = params[:message] || nil + result = CreateTagService.new(user_project, current_user). + execute(params[:tag_name], params[:ref], message, params[:release_description]) + + if result[:status] == :success + present result[:tag], + with: Entities::RepoTag, + project: user_project + else + render_api_error!(result[:message], 400) + end + end + + # Add release notes to tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # description (required) - Release notes with markdown support + # Example Request: + # POST /projects/:id/repository/tags/:tag_name/release + post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + authorize_push_project + required_attributes! [:description] + result = CreateReleaseService.new(user_project, current_user). + execute(params[:tag_name], params[:description]) + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end + + # Updates a release notes of a tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # description (required) - Release notes with markdown support + # Example Request: + # PUT /projects/:id/repository/tags/:tag_name/release + put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + authorize_push_project + required_attributes! [:description] + result = UpdateReleaseService.new(user_project, current_user). + execute(params[:tag_name], params[:description]) + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end + end + end +end diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb new file mode 100644 index 00000000000..2781f1cf191 --- /dev/null +++ b/lib/api/triggers.rb @@ -0,0 +1,48 @@ +module API + # Triggers API + class Triggers < Grape::API + resource :projects do + # Trigger a GitLab project build + # + # Parameters: + # id (required) - The ID of a CI project + # ref (required) - The name of project's branch or tag + # token (required) - The uniq token of trigger + # variables (optional) - The list of variables to be injected into build + # Example Request: + # POST /projects/:id/trigger/builds + post ":id/trigger/builds" do + required_attributes! [:ref, :token] + + project = Project.find_with_namespace(params[:id]) || Project.find_by(id: 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] + if variables + unless variables.is_a?(Hash) + render_api_error!('variables needs to be a hash', 400) + end + + 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 + + # convert variables from Mash to Hash + variables = variables.to_h + 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: Entities::TriggerRequest + else + errors = 'No builds created' + render_api_error!(errors, 400) + end + end + end + end +end diff --git a/lib/api/users.rb b/lib/api/users.rb index a98d668e02d..0d7813428e2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -8,11 +8,17 @@ module API # # Example Request: # GET /users + # GET /users?search=Admin + # GET /users?username=root get do - @users = User.all - @users = @users.active if params[:active].present? - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users + if params[:username].present? + @users = User.where(username: params[:username]) + else + @users = User.all + @users = @users.active if params[:active].present? + @users = @users.search(params[:search]) if params[:search].present? + @users = paginate @users + end if current_user.is_admin? present @users, with: Entities::UserFull @@ -33,7 +39,7 @@ module API if current_user.is_admin? present @user, with: Entities::UserFull else - present @user, with: Entities::UserBasic + present @user, with: Entities::User end end |