Commit 970f41cf authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'add-api-shared_runners_minutes_limit' into 'master'

Add shared_runners_minutes_limit to groups and users API

See merge request !1942
parents cba2826e c14bc19d
......@@ -15,6 +15,9 @@ module EE
# column directly.
validate :auditor_requires_license_add_on, if: :auditor
validate :cannot_be_admin_and_auditor
delegate :shared_runners_minutes_limit, :shared_runners_minutes_limit=,
to: :namespace
end
module ClassMethods
......
......@@ -65,7 +65,7 @@ class User < ActiveRecord::Base
#
# Namespace for personal projects
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id
has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, autosave: true
# Profile
has_many :keys, -> do
......
---
title: Add shared_runners_minutes_limit to groups and users API
merge_request: 1942
author:
......@@ -140,6 +140,7 @@ Example response:
"full_name": "Twitter",
"full_path": "twitter",
"parent_id": null,
"shared_runners_minutes_limit": 133,
"projects": [
{
"id": 7,
......@@ -290,6 +291,7 @@ Parameters:
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group
- `request_access_enabled` (optional) - Allow users to request member access.
- `parent_id` (optional) - The parent group id for creating nested group.
- `shared_runners_minutes_limit` (optional) - (admin-only) Pipeline minutes quota for this group
## Transfer project to group
......@@ -323,6 +325,7 @@ PUT /groups/:id
| `visibility` | string | no | The visibility level of the group. Can be `private`, `internal`, or `public`. |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
| `request_access_enabled` | boolean | no | Allow users to request member access. |
| `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group |
```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/groups/5?name=Experimental"
......
......@@ -220,7 +220,8 @@ Parameters:
"can_create_group": true,
"can_create_project": true,
"two_factor_enabled": true,
"external": false
"external": false,
"shared_runners_minutes_limit": 133
}
```
......@@ -253,6 +254,7 @@ Parameters:
- `can_create_group` (optional) - User can create groups - true or false
- `confirm` (optional) - Require confirmation - true (default) or false
- `external` (optional) - Flags the user as external - true or false(default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
## User modification
......@@ -281,6 +283,7 @@ Parameters:
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `external` (optional) - Flags the user as external - true or false(default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
......
......@@ -41,6 +41,9 @@ module API
expose :can_create_project?, as: :can_create_project
expose :two_factor_enabled?, as: :two_factor_enabled
expose :external
# EE-only
expose :shared_runners_minutes_limit
end
class UserWithPrivateDetails < UserPublic
......@@ -188,6 +191,9 @@ module API
class GroupDetail < Group
expose :projects, using: Entities::Project
expose :shared_projects, using: Entities::Project
# EE-only
expose :shared_runners_minutes_limit
end
class RepoCommit < Grape::Entity
......
......@@ -17,6 +17,7 @@ module API
optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group'
optional :ldap_cn, type: String, desc: 'LDAP Common Name'
optional :ldap_access, type: Integer, desc: 'A valid access level'
optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group'
all_or_none_of :ldap_cn, :ldap_access
end
......@@ -89,6 +90,9 @@ module API
group_access: params.delete(:ldap_access)
}
# EE
authenticated_as_admin! if params[:shared_runners_minutes_limit]
group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
if group.persisted?
......@@ -100,7 +104,7 @@ module API
)
end
present group, with: Entities::Group, current_user: current_user
present group, with: Entities::GroupDetail, current_user: current_user
else
render_api_error!("Failed to save group #{group.errors.messages}", 400)
end
......@@ -118,13 +122,18 @@ 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,
:lfs_enabled, :request_access_enabled
end
put ':id' do
group = find_group!(params[:id])
authorize! :admin_group, group
# EE
if params[:shared_runners_minutes_limit].present? &&
group.shared_runners_minutes_limit.to_i !=
params[:shared_runners_minutes_limit].to_i
authenticated_as_admin!
end
if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
present group, with: Entities::GroupDetail, current_user: current_user
else
......
......@@ -30,6 +30,9 @@ module API
optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
all_or_none_of :extern_uid, :provider
# EE
optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
end
end
......@@ -127,10 +130,6 @@ module API
optional :name, type: String, desc: 'The name of the user'
optional :username, type: String, desc: 'The username of the user'
use :optional_attributes
at_least_one_of :email, :password, :name, :username, :skype, :linkedin,
:twitter, :website_url, :organization, :projects_limit,
:extern_uid, :provider, :bio, :location, :admin,
:can_create_group, :confirm, :external
end
put ":id" do
authenticated_as_admin!
......
......@@ -13,6 +13,14 @@ describe User, models: true do
it { is_expected.to include_module(TokenAuthenticatable) }
end
describe 'delegations' do
it { is_expected.to delegate_method(:path).to(:namespace).with_prefix }
# EE
it { is_expected.to delegate_method(:shared_runners_minutes_limit).to(:namespace) }
it { is_expected.to delegate_method(:shared_runners_minutes_limit=).to(:namespace).with_arguments(133) }
end
describe 'associations' do
it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).dependent(:destroy) }
......
......@@ -272,6 +272,25 @@ describe API::Groups do
expect(response).to have_http_status(404)
end
# EE
it 'returns 403 for updating shared_runners_minutes_limit' do
expect do
put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
end.not_to change { group1.shared_runners_minutes_limit }
expect(response).to have_http_status(403)
end
it 'returns 200 if shared_runners_minutes_limit is not changing' do
group1.update(shared_runners_minutes_limit: 133)
expect do
put api("/groups/#{group1.id}", user1), shared_runners_minutes_limit: 133
end.not_to change { group1.shared_runners_minutes_limit }
expect(response).to have_http_status(200)
end
end
context 'when authenticated as the admin' do
......@@ -281,6 +300,17 @@ describe API::Groups do
expect(response).to have_http_status(200)
expect(json_response['name']).to eq(new_group_name)
end
# EE
it 'updates the group for shared_runners_minutes_limit' do
expect do
put api("/groups/#{group1.id}", admin), shared_runners_minutes_limit: 133
end.to change { group1.reload.shared_runners_minutes_limit }
.from(nil).to(133)
expect(response).to have_http_status(200)
expect(json_response['shared_runners_minutes_limit']).to eq(133)
end
end
context 'when authenticated as an user that can see the group' do
......@@ -479,23 +509,36 @@ describe API::Groups do
group_attributes = attributes_for(:group, ldap_cn: 'ldap-group', ldap_access: Gitlab::Access::DEVELOPER)
expect { post api("/groups", admin), group_attributes }.to change{ LdapGroupLink.count }.by(1)
end
# EE
context 'when shared_runners_minutes_limit is given' do
context 'when the current user is not an admin' do
it "does not create a group with shared_runners_minutes_limit" do
group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
expect do
post api("/groups", user3), group
end.not_to change { Group.count }
expect(response).to have_http_status(403)
end
end
describe "PUT /groups" do
context "when authenticated as user without group permissions" do
it "does not create group" do
put api("/groups/#{group2.id}", user1), attributes_for(:group)
expect(response.status).to eq(404)
context 'when the current user is an admin' do
it "creates a group with shared_runners_minutes_limit" do
group = attributes_for(:group, { shared_runners_minutes_limit: 133 })
expect do
post api("/groups", admin), group
end.to change { Group.count }.by(1)
created_group = Group.find(json_response['id'])
expect(created_group.shared_runners_minutes_limit).to eq(133)
expect(response).to have_http_status(201)
expect(json_response['shared_runners_minutes_limit']).to eq(133)
end
end
context "when authenticated as user with group permissions" do
it "updates group" do
group2.update(owner: user2)
put api("/groups/#{group2.id}", user2), { name: 'Renamed' }
expect(response.status).to eq(200)
expect(group2.reload.name).to eq('Renamed')
end
end
end
......
......@@ -425,6 +425,17 @@ describe API::Users do
expect(user.reload.external?).to be_truthy
end
# EE
it "updates shared_runners_minutes_limit" do
expect do
put api("/users/#{user.id}", admin), { shared_runners_minutes_limit: 133 }
end.to change { user.reload.shared_runners_minutes_limit }
.from(nil).to(133)
expect(response).to have_http_status(200)
expect(json_response['shared_runners_minutes_limit']).to eq(133)
end
it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response).to have_http_status(200)
......@@ -438,11 +449,24 @@ describe API::Users do
expect(user.reload.email).not_to eq('invalid email')
end
it "is not available for non admin users" do
context 'when the current user is not an admin' do
it "is not available" do
expect do
put api("/users/#{user.id}", user), attributes_for(:user)
end.not_to change { user.reload.attributes }
expect(response).to have_http_status(403)
end
it "cannot update their own shared_runners_minutes_limit" do
expect do
put api("/users/#{user.id}", user), { shared_runners_minutes_limit: 133 }
end.not_to change { user.reload.shared_runners_minutes_limit }
expect(response).to have_http_status(403)
end
end
it "returns 404 for non-existing user" do
put api("/users/999999", admin), { bio: 'update should fail' }
expect(response).to have_http_status(404)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment