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
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)
end
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 })
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')
expect do
post api("/groups", user3), group
end.not_to change { Group.count }
expect(response).to have_http_status(403)
end
end
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
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,9 +449,22 @@ describe API::Users do
expect(user.reload.email).not_to eq('invalid email')
end
it "is not available for non admin users" do
put api("/users/#{user.id}", user), attributes_for(:user)
expect(response).to have_http_status(403)
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
......
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