Commit 883e5e0d authored by Matija Čupić's avatar Matija Čupić

Add subscription count to application limits

Moves the subscription count to the model with the Limitable concern.
parent 1b1cf299
# frozen_string_literal: true
class AddProjectSubscriptionsToPlanLimits < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column(:plan_limits, :ci_project_subscriptions, :integer, default: 0, null: false)
end
end
# frozen_string_literal: true
class InsertProjectSubscriptionsPlanLimits < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
return if Rails.env.test?
if Gitlab.com?
create_or_update_plan_limit('ci_project_subscriptions', 'free', 2)
create_or_update_plan_limit('ci_project_subscriptions', 'bronze', 2)
create_or_update_plan_limit('ci_project_subscriptions', 'silver', 2)
create_or_update_plan_limit('ci_project_subscriptions', 'gold', 2)
else
create_or_update_plan_limit('ci_project_subscriptions', 'default', 2)
end
end
def down
return if Rails.env.test?
if Gitlab.com?
create_or_update_plan_limit('ci_project_subscriptions', 'free', 0)
create_or_update_plan_limit('ci_project_subscriptions', 'bronze', 0)
create_or_update_plan_limit('ci_project_subscriptions', 'silver', 0)
create_or_update_plan_limit('ci_project_subscriptions', 'gold', 0)
else
create_or_update_plan_limit('ci_project_subscriptions', 'default', 0)
end
end
end
...@@ -3131,6 +3131,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_160823) do ...@@ -3131,6 +3131,7 @@ ActiveRecord::Schema.define(version: 2020_03_04_160823) do
t.integer "ci_active_jobs", default: 0, null: false t.integer "ci_active_jobs", default: 0, null: false
t.integer "project_hooks", default: 0, null: false t.integer "project_hooks", default: 0, null: false
t.integer "group_hooks", default: 0, null: false t.integer "group_hooks", default: 0, null: false
t.integer "ci_project_subscriptions", default: 0, null: false
t.index ["plan_id"], name: "index_plan_limits_on_plan_id", unique: true t.index ["plan_id"], name: "index_plan_limits_on_plan_id", unique: true
end end
......
...@@ -87,6 +87,28 @@ Plan.default.limits.update!(ci_active_jobs: 500) ...@@ -87,6 +87,28 @@ Plan.default.limits.update!(ci_active_jobs: 500)
NOTE: **Note:** Set the limit to `0` to disable it. NOTE: **Note:** Set the limit to `0` to disable it.
### Number of CI/CD subscriptions to a project
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9045) in GitLab 12.9.
The total number of subscriptions can be limited per project. This limit is
checked each time a new subscription is created.
If a new subscription would cause the total number of subscription to exceed the
limit, the subscription will be considered invalid.
- On GitLab.com different [limits are defined per plan](../user/gitlab_com/index.md#gitlab-cicd) and they affect all projects under that plan.
- On [GitLab Starter](https://about.gitlab.com/pricing/#self-managed) tier or higher self-hosted installations, this limit is defined for the `default` plan that affects all projects.
To set this limit on a self-hosted installation, run the following in the
[GitLab Rails console](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session):
```ruby
Plan.default.limits.update!(ci_project_subscriptions: 500)
```
NOTE: **Note:** Set the limit to `0` to disable it.
## Environment data on Deploy Boards ## Environment data on Deploy Boards
[Deploy Boards](../user/project/deploy_boards.md) load information from Kubernetes about [Deploy Boards](../user/project/deploy_boards.md) load information from Kubernetes about
......
...@@ -39,6 +39,12 @@ limit values. It's recommended to create separate migration script files. ...@@ -39,6 +39,12 @@ limit values. It's recommended to create separate migration script files.
create_or_update_plan_limit('project_hooks', 'gold', 100) create_or_update_plan_limit('project_hooks', 'gold', 100)
``` ```
NOTE: **Note:** Some plans exist only on GitLab.com. You can check if the
migration is running on GitLab.com with `Gitlab.com?`.
NOTE: **Note:** The test environment doesn't have any plans. You can check if a
migration is running in a test environment with `Rails.env.test?`
### Plan limits validation ### Plan limits validation
#### Get current limit #### Get current limit
...@@ -93,3 +99,20 @@ it_behaves_like 'includes Limitable concern' do ...@@ -93,3 +99,20 @@ it_behaves_like 'includes Limitable concern' do
subject { build(:project_hook, project: create(:project)) } subject { build(:project_hook, project: create(:project)) }
end end
``` ```
### Subscription Plans
Self-hosted:
- `default` - Everyone
Hosted:
- `free` - Everyone
- `bronze`- Namespaces with a Bronze subscription
- `silver` - Namespaces with a Silver subscription
- `gold` - Namespaces with a Gold subscription
NOTE: **Note:** Hosted plans exist only on GitLab.com.
NOTE: **Note:** The test environment doesn't have any plans.
...@@ -6,7 +6,6 @@ class Projects::SubscriptionsController < Projects::ApplicationController ...@@ -6,7 +6,6 @@ class Projects::SubscriptionsController < Projects::ApplicationController
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :feature_ci_project_subscriptions! before_action :feature_ci_project_subscriptions!
before_action :authorize_upstream_project!, only: [:create] before_action :authorize_upstream_project!, only: [:create]
before_action :check_subscription_count!, only: [:create]
def create def create
subscription = project.upstream_project_subscriptions.create(upstream_project: upstream_project) subscription = project.upstream_project_subscriptions.create(upstream_project: upstream_project)
...@@ -14,7 +13,7 @@ class Projects::SubscriptionsController < Projects::ApplicationController ...@@ -14,7 +13,7 @@ class Projects::SubscriptionsController < Projects::ApplicationController
if subscription.persisted? if subscription.persisted?
flash[:notice] = _('Subscription successfully created.') flash[:notice] = _('Subscription successfully created.')
else else
flash[:alert] = _('Subscription creation failed because the specified project is not public.') flash[:alert] = subscription.errors.full_messages
end end
redirect_to project_settings_ci_cd_path(project) redirect_to project_settings_ci_cd_path(project)
...@@ -52,11 +51,4 @@ class Projects::SubscriptionsController < Projects::ApplicationController ...@@ -52,11 +51,4 @@ class Projects::SubscriptionsController < Projects::ApplicationController
flash[:warning] = _('This project path either does not exist or you do not have access.') flash[:warning] = _('This project path either does not exist or you do not have access.')
redirect_to project_settings_ci_cd_path(project) redirect_to project_settings_ci_cd_path(project)
end end
def check_subscription_count!
return if project.upstream_project_subscriptions.count < 2
flash[:warning] = _('Subscription limit reached.')
redirect_to project_settings_ci_cd_path(project)
end
end end
...@@ -3,8 +3,13 @@ ...@@ -3,8 +3,13 @@
module Ci module Ci
module Subscriptions module Subscriptions
class Project < ApplicationRecord class Project < ApplicationRecord
include ::Limitable
self.table_name = "ci_subscriptions_projects" self.table_name = "ci_subscriptions_projects"
self.limit_name = 'ci_project_subscriptions'
self.limit_scope = :upstream_project
belongs_to :downstream_project, class_name: '::Project', optional: false belongs_to :downstream_project, class_name: '::Project', optional: false
belongs_to :upstream_project, class_name: '::Project', optional: false belongs_to :upstream_project, class_name: '::Project', optional: false
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- default_branch_docs = link_to(_("default branch"), help_page_path('user/project/repository/branches', anchor: 'default-branch')) - default_branch_docs = link_to(_("default branch"), help_page_path('user/project/repository/branches', anchor: 'default-branch'))
= _("A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project.").html_safe % { default_branch_docs: default_branch_docs } = _("A subscription will trigger a new pipeline on the default branch of this project when a pipeline successfully completes for a new tag on the %{default_branch_docs} of the subscribed project.").html_safe % { default_branch_docs: default_branch_docs }
%p %p
= _("There is a limit of 2 subscriptions from or to a project.") = _("There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project.").html_safe % { ci_project_subscriptions_limit: @project.actual_limits.ci_project_subscriptions }
.settings-content .settings-content
= render 'projects/settings/subscriptions/index' = render 'projects/settings/subscriptions/index'
...@@ -16,6 +16,11 @@ describe Projects::SubscriptionsController do ...@@ -16,6 +16,11 @@ describe Projects::SubscriptionsController do
let(:upstream_project) { create(:project, :public) } let(:upstream_project) { create(:project, :public) }
before do
plan_limits = create(:plan_limits, :default_plan)
plan_limits.update(ci_project_subscriptions: 2)
end
context 'when user is authorized' do context 'when user is authorized' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -50,19 +55,19 @@ describe Projects::SubscriptionsController do ...@@ -50,19 +55,19 @@ describe Projects::SubscriptionsController do
end end
end end
context 'when subscription count is on the limit' do context 'when subscription count is above the limit' do
before do before do
create_list(:ci_subscriptions_project, 2, downstream_project: project) create_list(:ci_subscriptions_project, 2, upstream_project: upstream_project)
end end
it 'does not create a new subscription' do it 'does not create a new subscription' do
expect { post_create }.not_to change { project.upstream_project_subscriptions.count }.from(2) expect { post_create }.not_to change { project.upstream_project_subscriptions.count }.from(0)
end end
it 'sets the flash' do it 'sets the flash' do
post_create post_create
expect(response).to set_flash[:warning].to('Subscription limit reached.') expect(response).to set_flash[:alert].to(['Maximum number of ci project subscriptions (2) exceeded'])
end end
it 'redirects to ci_cd settings' do it 'redirects to ci_cd settings' do
...@@ -85,7 +90,7 @@ describe Projects::SubscriptionsController do ...@@ -85,7 +90,7 @@ describe Projects::SubscriptionsController do
it 'sets the flash' do it 'sets the flash' do
post_create post_create
expect(response).to set_flash[:alert].to('Subscription creation failed because the specified project is not public.') expect(response).to set_flash[:alert].to(['Upstream project needs to be public'])
end end
it 'redirects to ci_cd settings' do it 'redirects to ci_cd settings' do
......
...@@ -3,18 +3,24 @@ ...@@ -3,18 +3,24 @@
require 'spec_helper' require 'spec_helper'
describe Ci::Subscriptions::Project do describe Ci::Subscriptions::Project do
let!(:subscription) { create(:ci_subscriptions_project) } let(:upstream_project) { create(:project, :public) }
describe 'Relations' do describe 'Relations' do
it { is_expected.to belong_to(:downstream_project).required } it { is_expected.to belong_to(:downstream_project).required }
it { is_expected.to belong_to(:upstream_project).required } it { is_expected.to belong_to(:upstream_project).required }
end end
it_behaves_like 'includes Limitable concern' do
subject { build(:ci_subscriptions_project, upstream_project: upstream_project) }
end
describe 'Validations' do describe 'Validations' do
let!(:subscription) { create(:ci_subscriptions_project, upstream_project: upstream_project) }
it { is_expected.to validate_uniqueness_of(:upstream_project_id).scoped_to(:downstream_project_id) } it { is_expected.to validate_uniqueness_of(:upstream_project_id).scoped_to(:downstream_project_id) }
it 'validates that upstream project is public' do it 'validates that upstream project is public' do
subscription.upstream_project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE) upstream_project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
expect(subscription).not_to be_valid expect(subscription).not_to be_valid
end end
......
...@@ -7,7 +7,7 @@ RSpec.shared_examples 'includes Limitable concern' do ...@@ -7,7 +7,7 @@ RSpec.shared_examples 'includes Limitable concern' do
it { is_expected.to be_a(Limitable) } it { is_expected.to be_a(Limitable) }
context 'without plan limits configured' do context 'without plan limits configured' do
it 'can create new group hooks' do it 'can create new models' do
expect { subject.save }.to change { described_class.count } expect { subject.save }.to change { described_class.count }
end end
end end
......
...@@ -18810,15 +18810,9 @@ msgstr "" ...@@ -18810,15 +18810,9 @@ msgstr ""
msgid "Subscription" msgid "Subscription"
msgstr "" msgstr ""
msgid "Subscription creation failed because the specified project is not public."
msgstr ""
msgid "Subscription deletion failed." msgid "Subscription deletion failed."
msgstr "" msgstr ""
msgid "Subscription limit reached."
msgstr ""
msgid "Subscription successfully applied to \"%{group_name}\"" msgid "Subscription successfully applied to \"%{group_name}\""
msgstr "" msgstr ""
...@@ -19738,7 +19732,7 @@ msgstr "" ...@@ -19738,7 +19732,7 @@ msgstr ""
msgid "There are no unstaged changes" msgid "There are no unstaged changes"
msgstr "" msgstr ""
msgid "There is a limit of 2 subscriptions from or to a project." msgid "There is a limit of %{ci_project_subscriptions_limit} subscriptions from or to a project."
msgstr "" msgstr ""
msgid "There is already a repository with that name on disk" msgid "There is already a repository with that name on disk"
......
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