diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2017-06-28 15:53:12 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2017-06-28 15:53:12 +0800 |
commit | 35674fcd4732681286224c1c5fc92386ff53db7f (patch) | |
tree | 31585e8a89ebbf384bf9a9a61e9813331df8cdf7 /lib/api/projects.rb | |
parent | 07365e518330289149dd2135424c49fad19f401d (diff) | |
parent | 08ad0af49c017d740b43588c0809b3811d25a448 (diff) | |
download | gitlab-ce-35674fcd4732681286224c1c5fc92386ff53db7f.tar.gz |
Merge remote-tracking branch 'upstream/master' into 15041-Add-Custom-CI-Config-Path15041-Add-Custom-CI-Config-Path
* upstream/master: (12506 commits)
Update CHANGELOG.md for 9.3.2
Update architecture.md
Fix changelog entry file extension
Fix head pipeline stored in merge request for external pipelines
updated gitlab-ci.yml to compile locale
Ignore JSON files generated from PO files
Update mmap2 gem tha disables mmap_obj.gsub! as current implementation uses method that is no longer part of Ruby API
Disable rainbow during SimpleExecutor specs to have consistence
Slightly refactor pipeline schedules form in preparation for additions
Resolve "Submitting reply to existing diff discussion using Cmd/Ctrl+Enter submits twice and refreshes page"
Make the SimpleExecutor rescue exceptions in the executing Checks
Resolve "Unable to access edit comment from dropdown menu in certain screen sizes"
Update changelog item
revert removal of requestAnimationFrame and move to a separate MR/discussion
rename getEmojiCategoryMap and remove unnecessary parameter
Action Buttons on Prio Labels working again by setting pointer events to none on…
Remove 'contains' option from Commit.find_all
Remove Gitlab::Git::Repository#find_all
Use latest chrome and chrome driver in GitLab QA
Polish sidebar toggle
...
Diffstat (limited to 'lib/api/projects.rb')
-rw-r--r-- | lib/api/projects.rb | 649 |
1 files changed, 297 insertions, 352 deletions
diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 291e7b689bf..20707e97e53 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,311 +1,262 @@ module API # Projects API class Projects < Grape::API - before { authenticate! } + include PaginationParams + + before { authenticate_non_get! } + + helpers do + params :optional_params_ce do + optional :description, type: String, desc: 'The description of the project' + optional :ci_config_file, type: String, desc: 'The path to CI config file. Default to `.gitlab-ci.yml`' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + 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, 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' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + optional :tag_list, type: Array[String], desc: 'The list of tags for a project' + optional :avatar, type: File, desc: 'Avatar image for project' + optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line' + end + + params :optional_params do + use :optional_params_ce + end + + params :statistics_params do + optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' + end + end - resource :projects, requirements: { id: /[^\/]+/ } do + resource :projects do helpers do - def map_public_to_visibility_level(attrs) - publik = attrs.delete(:public) - if publik.present? && !attrs[:visibility_level].present? - publik = to_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 + params :collection_params do + use :sort_params + use :filter_params + use :pagination + + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + end + + params :sort_params do + 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' + end + + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, + desc: 'Limit by visibility' + 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' + optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' + optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' + end + + def present_projects(options = {}) + projects = ProjectsFinder.new(current_user: current_user, params: project_finder_params).execute + projects = reorder_projects(projects) + projects = projects.with_statistics if params[:statistics] + projects = projects.with_issues_enabled if params[:with_issues_enabled] + projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] + + options = options.reverse_merge( + with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, + statistics: params[:statistics], + current_user: current_user + ) + options[:with] = Entities::BasicProjectDetails if params[:simple] + + present paginate(projects), options end end - # Get a projects list for authenticated user - # - # Example Request: - # GET /projects + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :collection_params + use :statistics_params + end get do - projects = current_user.authorized_projects - projects = filter_projects(projects) - projects = paginate projects - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - - present projects, with: entity, user: current_user - end - - # Get a list of visible projects for authenticated user - # - # Example Request: - # GET /projects/visible - get '/visible' do - projects = ProjectsFinder.new.execute(current_user) - projects = filter_projects(projects) - projects = paginate projects - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - - present projects, with: entity, user: current_user - end - - # Get an owned projects list for authenticated user - # - # Example Request: - # GET /projects/owned - get '/owned' do - 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.viewable_starred_projects - projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::Project, user: current_user - end - - # Get all projects for admin user - # - # Example Request: - # GET /projects/all - get '/all' do - authenticated_as_admin! - projects = Project.all - projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user - end - - # Get a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id - get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project) - end - - # Get events for a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/events - get ":id/events" do - events = paginate user_project.events.recent - present events, with: Entities::Event - end - - # Create new project - # - # Parameters: - # name (required) - name for new project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_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 - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # ci_config_file (optional) - # Example Request - # POST /projects + present_projects + end + + desc 'Create new project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + at_least_one_of :name, :path + use :optional_params + use :create_params + end post do - required_attributes! [:name] - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :ci_config_file, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :namespace_id, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(current_user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + attrs = declared_params(include_missing: false) + attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - if @project.errors[:limit_reached].present? - error!(@project.errors[:limit_reached], 403) + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) end - render_validation_error!(@project) + render_validation_error!(project) end end - # Create new project for a specified user. Only available to admin users. - # - # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # ci_config_file (optional) - # Example Request - # POST /projects/user/:user_id + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :path, type: String, desc: 'The path of the repository' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end post "user/:user_id" do authenticated_as_admin! - user = User.find(params[:user_id]) - attrs = attributes_for_keys [:builds_enabled, - :ci_config_file, - :default_branch, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = declared_params(include_missing: false) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - render_validation_error!(@project) + render_validation_error!(project) end end + end - # Fork new project for the current user or provided namespace. - # - # Parameters: - # id (required) - The ID of a project - # namespace (optional) - The ID or name of the namespace that the project will be forked into. - # Example Request - # POST /projects/fork/:id - post 'fork/:id' do - attrs = {} - namespace_id = params[:namespace] + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: %r{[^/]+} } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + params do + use :statistics_params + end + get ":id" do + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present user_project, with: entity, current_user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project), statistics: params[:statistics] + end + + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end + post ':id/fork' do + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] if namespace_id.present? - namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end - unless namespace && can?(current_user, :create_projects, namespace) + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') end - - attrs[:namespace] = namespace end - @forked_project = - ::Projects::ForkService.new(user_project, - current_user, - attrs).execute + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute - if @forked_project.errors.any? - conflict!(@forked_project.errors.messages) + if forked_project.errors.any? + conflict!(forked_project.errors.messages) else - present @forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @forked_project) + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) end end - # Update an existing project - # - # Parameters: - # id (required) - the id of a project - # name (optional) - name of a project - # path (optional) - path of a project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - visibility level of a project - # public_builds (optional) - # lfs_enabled (optional) - # ci_config_file (optional) - # Example Request - # PUT /projects/:id + desc 'Update an existing project' do + success Entities::Project + end + params do + # CE + at_least_one_of_ce = + [ + :jobs_enabled, + :container_registry_enabled, + :default_branch, + :description, + :issues_enabled, + :lfs_enabled, + :merge_requests_enabled, + :name, + :only_allow_merge_if_all_discussions_are_resolved, + :only_allow_merge_if_pipeline_succeeds, + :path, + :printing_merge_request_link_enabled, + :public_builds, + :request_access_enabled, + :shared_runners_enabled, + :snippets_enabled, + :tag_list, + :visibility, + :wiki_enabled + ] + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + + use :optional_params + at_least_one_of(*at_least_one_of_ce) + end put ':id' do - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :ci_config_file, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled] - attrs = map_public_to_visibility_level(attrs) authorize_admin_project + attrs = declared_params(include_missing: false) authorize! :rename_project, user_project if attrs[:name].present? - if attrs[:visibility_level].present? - authorize! :change_visibility_level, user_project - end + authorize! :change_visibility_level, user_project if attrs[:visibility].present? - ::Projects::UpdateService.new(user_project, - current_user, attrs).execute + attrs[:builds_enabled] = attrs.delete(:jobs_enabled) if attrs.key?(:jobs_enabled) - if user_project.errors.any? - render_validation_error!(user_project) - else + result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute + + if result[:status] == :success present user_project, with: Entities::Project, user_can_admin_project: can?(current_user, :admin_project, user_project) + else + render_validation_error!(user_project) end end - # Archive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/archive + desc 'Archive a project' do + success Entities::Project + end post ':id/archive' do authorize!(:archive_project, user_project) @@ -314,12 +265,9 @@ module API present user_project, with: Entities::Project end - # Unarchive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/unarchive + desc 'Unarchive a project' do + success Entities::Project + end post ':id/unarchive' do authorize!(:archive_project, user_project) @@ -328,12 +276,9 @@ module API present user_project, with: Entities::Project end - # Star project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/star + desc 'Star a project' do + success Entities::Project + end post ':id/star' do if current_user.starred?(user_project) not_modified! @@ -345,13 +290,10 @@ module API end end - # Unstar project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/star - delete ':id/star' do + desc 'Unstar a project' do + success Entities::Project + end + post ':id/unstar' do if current_user.starred?(user_project) current_user.toggle_star(user_project) user_project.reload @@ -362,71 +304,63 @@ module API end end - # Remove project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id + desc 'Remove a project' delete ":id" do authorize! :remove_project, user_project ::Projects::DestroyService.new(user_project, current_user, {}).async_execute + + accepted! end - # Mark this project as forked from another - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # forked_from_id: (required) - The ID of the project it was forked from - # Example Request: - # POST /projects/:id/fork/:forked_from_id + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end post ":id/fork/:forked_from_id" do authenticated_as_admin! - forked_from_project = find_project(params[:forked_from_id]) - unless forked_from_project.nil? - if user_project.forked_from_project.nil? - user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) - else - render_api_error!("Project already forked", 409) - end + + forked_from_project = find_project!(params[:forked_from_id]) + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) else - not_found!("Source Project") + render_api_error!("Project already forked", 409) end end - # Remove a forked_from relationship - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # Example Request: - # DELETE /projects/:id/fork + desc 'Remove a forked_from relationship' delete ":id/fork" do authorize! :remove_fork_project, user_project + if user_project.forked? user_project.forked_project_link.destroy + else + not_modified! end end - # Share project with group - # - # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group - # group_access (required) - Level of permissions for sharing - # expires_at (optional) - Share expiration date - # - # Example Request: - # POST /projects/:id/share + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end post ":id/share" do authorize! :admin_project, user_project - required_attributes! [:group_id, :group_access] - attrs = attributes_for_keys [:group_id, :group_access, :expires_at] + group = Group.find_by_id(params[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(attrs) + link = user_project.project_group_links.new(declared_params(include_missing: false)) if link.save present link, with: Entities::ProjectGroupLink @@ -435,40 +369,51 @@ module API end end - # Upload a file - # - # Parameters: - # id: (required) - The ID of the project - # file: (required) - The file to be uploaded - post ":id/uploads" do - ::Projects::UploadService.new(user_project, params[:file]).execute + params do + requires :group_id, type: Integer, desc: 'The ID of the group' end + delete ":id/share/:group_id" do + authorize! :admin_project, user_project + + link = user_project.project_group_links.find_by(group_id: params[:group_id]) + not_found!('Group Link') unless link - # search for projects current_user has access to - # - # Parameters: - # query (required) - A string contained in the project name - # per_page (optional) - number of projects to return per page - # page (optional) - the page to retrieve - # Example Request: - # GET /projects/search/:query - get "/search/:query" do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(project_order_by => project_sort) + link.destroy + end - present paginate(projects), with: Entities::Project + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end + post ":id/uploads" do + UploadService.new(user_project, params[:file]).execute end - # Get a users list - # - # Example Request: - # GET /users + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination + end get ':id/users' do - @users = User.where(id: user_project.team.users.map(&:id)) - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users - present @users, with: Entities::UserBasic + users = user_project.team.users + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic + end + + desc 'Start the housekeeping task for a project' do + detail 'This feature was introduced in GitLab 9.0.' + end + post ':id/housekeeping' do + authorize_admin_project + + begin + ::Projects::HousekeepingService.new(user_project).execute + rescue ::Projects::HousekeepingService::LeaseTaken => error + conflict!(error.message) + end end end end |