diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/api/api.rb | 3 | ||||
-rw-r--r-- | lib/api/feature_flag_scopes.rb | 158 | ||||
-rw-r--r-- | lib/api/feature_flags.rb | 266 | ||||
-rw-r--r-- | lib/api/feature_flags_user_lists.rb | 100 | ||||
-rw-r--r-- | lib/backup/repositories.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml | 3 | ||||
-rw-r--r-- | lib/gitlab/graphql/pagination/keyset/order_info.rb | 9 | ||||
-rw-r--r-- | lib/google_api/cloud_platform/client.rb | 2 |
8 files changed, 541 insertions, 4 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 546d726243e..417d4d66aca 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -153,6 +153,9 @@ module API mount ::API::Environments mount ::API::ErrorTracking mount ::API::Events + mount ::API::FeatureFlags + mount ::API::FeatureFlagScopes + mount ::API::FeatureFlagsUserLists mount ::API::Features mount ::API::Files mount ::API::FreezePeriods diff --git a/lib/api/feature_flag_scopes.rb b/lib/api/feature_flag_scopes.rb new file mode 100644 index 00000000000..4a42dbc1aea --- /dev/null +++ b/lib/api/feature_flag_scopes.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module API + class FeatureFlagScopes < Grape::API::Instance + include PaginationParams + + ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS = FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS + .merge(environment_scope: API::NO_SLASH_URL_PART_REGEX) + + before do + authorize_read_feature_flags! + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :feature_flag_scopes do + desc 'Get all effective feature flags under the environment' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::DetailedLegacyScope + end + params do + requires :environment, type: String, desc: 'The environment name' + end + get do + present scopes_for_environment, with: ::API::Entities::FeatureFlag::DetailedLegacyScope + end + end + + params do + requires :name, type: String, desc: 'The name of the feature flag' + end + resource 'feature_flags/:name', requirements: FeatureFlags::FEATURE_FLAG_ENDPOINT_REQUIREMENTS do + resource :scopes do + desc 'Get all scopes of a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::LegacyScope + end + params do + use :pagination + end + get do + present paginate(feature_flag.scopes), with: ::API::Entities::FeatureFlag::LegacyScope + end + + desc 'Create a scope of a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::LegacyScope + end + params do + requires :environment_scope, type: String, desc: 'The environment scope of the scope' + requires :active, type: Boolean, desc: 'Whether the scope is active' + requires :strategies, type: JSON, desc: 'The strategies of the scope' + end + post do + authorize_update_feature_flag! + + result = ::FeatureFlags::UpdateService + .new(user_project, current_user, scopes_attributes: [declared_params]) + .execute(feature_flag) + + if result[:status] == :success + present scope, with: ::API::Entities::FeatureFlag::LegacyScope + else + render_api_error!(result[:message], result[:http_status]) + end + end + + params do + requires :environment_scope, type: String, desc: 'URL-encoded environment scope' + end + resource ':environment_scope', requirements: ENVIRONMENT_SCOPE_ENDPOINT_REQUIREMENTS do + desc 'Get a scope of a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::LegacyScope + end + get do + present scope, with: ::API::Entities::FeatureFlag::LegacyScope + end + + desc 'Update a scope of a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::LegacyScope + end + params do + optional :active, type: Boolean, desc: 'Whether the scope is active' + optional :strategies, type: JSON, desc: 'The strategies of the scope' + end + put do + authorize_update_feature_flag! + + scope_attributes = declared_params.merge(id: scope.id) + + result = ::FeatureFlags::UpdateService + .new(user_project, current_user, scopes_attributes: [scope_attributes]) + .execute(feature_flag) + + if result[:status] == :success + updated_scope = result[:feature_flag].scopes + .find { |scope| scope.environment_scope == params[:environment_scope] } + + present updated_scope, with: ::API::Entities::FeatureFlag::LegacyScope + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Delete a scope from a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag::LegacyScope + end + delete do + authorize_update_feature_flag! + + param = { scopes_attributes: [{ id: scope.id, _destroy: true }] } + + result = ::FeatureFlags::UpdateService + .new(user_project, current_user, param) + .execute(feature_flag) + + if result[:status] == :success + status :no_content + else + render_api_error!(result[:message], result[:http_status]) + end + end + end + end + end + end + + helpers do + def authorize_read_feature_flags! + authorize! :read_feature_flag, user_project + end + + def authorize_update_feature_flag! + authorize! :update_feature_flag, feature_flag + end + + def feature_flag + @feature_flag ||= user_project.operations_feature_flags + .find_by_name!(params[:name]) + end + + def scope + @scope ||= feature_flag.scopes + .find_by_environment_scope!(CGI.unescape(params[:environment_scope])) + end + + def scopes_for_environment + Operations::FeatureFlagScope + .for_unleash_client(user_project, params[:environment]) + end + end + end +end diff --git a/lib/api/feature_flags.rb b/lib/api/feature_flags.rb new file mode 100644 index 00000000000..9e2be67f0de --- /dev/null +++ b/lib/api/feature_flags.rb @@ -0,0 +1,266 @@ +# frozen_string_literal: true + +module API + class FeatureFlags < Grape::API::Instance + include PaginationParams + + FEATURE_FLAG_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS + .merge(name: API::NO_SLASH_URL_PART_REGEX) + + before do + authorize_read_feature_flags! + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :feature_flags do + desc 'Get all feature flags of a project' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag + end + params do + optional :scope, type: String, desc: 'The scope of feature flags', + values: %w[enabled disabled] + use :pagination + end + get do + feature_flags = ::FeatureFlagsFinder + .new(user_project, current_user, declared_params(include_missing: false)) + .execute + + present_entity(paginate(feature_flags)) + end + + desc 'Create a new feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag + end + params do + requires :name, type: String, desc: 'The name of feature flag' + optional :description, type: String, desc: 'The description of the feature flag' + optional :active, type: Boolean, desc: 'Active/inactive value of the flag' + optional :version, type: String, desc: 'The version of the feature flag' + optional :scopes, type: Array do + requires :environment_scope, type: String, desc: 'The environment scope of the scope' + requires :active, type: Boolean, desc: 'Active/inactive of the scope' + requires :strategies, type: JSON, desc: 'The strategies of the scope' + end + optional :strategies, type: Array do + requires :name, type: String, desc: 'The strategy name' + requires :parameters, type: JSON, desc: 'The strategy parameters' + optional :scopes, type: Array do + requires :environment_scope, type: String, desc: 'The environment scope of the scope' + end + end + end + post do + authorize_create_feature_flag! + + attrs = declared_params(include_missing: false) + + ensure_post_version_2_flags_enabled! if attrs[:version] == 'new_version_flag' + + rename_key(attrs, :scopes, :scopes_attributes) + rename_key(attrs, :strategies, :strategies_attributes) + update_value(attrs, :strategies_attributes) do |strategies| + strategies.map { |s| rename_key(s, :scopes, :scopes_attributes) } + end + + result = ::FeatureFlags::CreateService + .new(user_project, current_user, attrs) + .execute + + if result[:status] == :success + present_entity(result[:feature_flag]) + else + render_api_error!(result[:message], result[:http_status]) + end + end + end + + params do + requires :feature_flag_name, type: String, desc: 'The name of the feature flag' + end + resource 'feature_flags/:feature_flag_name', requirements: FEATURE_FLAG_ENDPOINT_REQUIREMENTS do + desc 'Get a feature flag of a project' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag + end + get do + authorize_read_feature_flag! + + present_entity(feature_flag) + end + + desc 'Enable a strategy for a feature flag on an environment' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag + end + params do + requires :environment_scope, type: String, desc: 'The environment scope of the feature flag' + requires :strategy, type: JSON, desc: 'The strategy to be enabled on the scope' + end + post :enable do + not_found! unless Feature.enabled?(:feature_flag_api, user_project) + render_api_error!('Version 2 flags not supported', :unprocessable_entity) if new_version_flag_present? + + result = ::FeatureFlags::EnableService + .new(user_project, current_user, params).execute + + if result[:status] == :success + status :ok + present_entity(result[:feature_flag]) + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Disable a strategy for a feature flag on an environment' do + detail 'This feature is going to be introduced in GitLab 12.5 if `feature_flag_api` feature flag is removed' + success ::API::Entities::FeatureFlag + end + params do + requires :environment_scope, type: String, desc: 'The environment scope of the feature flag' + requires :strategy, type: JSON, desc: 'The strategy to be disabled on the scope' + end + post :disable do + not_found! unless Feature.enabled?(:feature_flag_api, user_project) + render_api_error!('Version 2 flags not supported', :unprocessable_entity) if feature_flag.new_version_flag? + + result = ::FeatureFlags::DisableService + .new(user_project, current_user, params).execute + + if result[:status] == :success + status :ok + present_entity(result[:feature_flag]) + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Update a feature flag' do + detail 'This feature will be introduced in GitLab 13.1 if feature_flags_new_version feature flag is removed' + success ::API::Entities::FeatureFlag + end + params do + optional :name, type: String, desc: 'The name of the feature flag' + optional :description, type: String, desc: 'The description of the feature flag' + optional :active, type: Boolean, desc: 'Active/inactive value of the flag' + optional :strategies, type: Array do + optional :id, type: Integer, desc: 'The strategy id' + optional :name, type: String, desc: 'The strategy type' + optional :parameters, type: JSON, desc: 'The strategy parameters' + optional :_destroy, type: Boolean, desc: 'Delete the strategy when true' + optional :scopes, type: Array do + optional :id, type: Integer, desc: 'The environment scope id' + optional :environment_scope, type: String, desc: 'The environment scope of the scope' + optional :_destroy, type: Boolean, desc: 'Delete the scope when true' + end + end + end + put do + not_found! unless feature_flags_new_version_enabled? + authorize_update_feature_flag! + render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) if feature_flag.legacy_flag? + + attrs = declared_params(include_missing: false) + + rename_key(attrs, :strategies, :strategies_attributes) + update_value(attrs, :strategies_attributes) do |strategies| + strategies.map { |s| rename_key(s, :scopes, :scopes_attributes) } + end + + result = ::FeatureFlags::UpdateService + .new(user_project, current_user, attrs) + .execute(feature_flag) + + if result[:status] == :success + present_entity(result[:feature_flag]) + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Delete a feature flag' do + detail 'This feature was introduced in GitLab 12.5' + success ::API::Entities::FeatureFlag + end + delete do + authorize_destroy_feature_flag! + + result = ::FeatureFlags::DestroyService + .new(user_project, current_user, declared_params(include_missing: false)) + .execute(feature_flag) + + if result[:status] == :success + present_entity(result[:feature_flag]) + else + render_api_error!(result[:message], result[:http_status]) + end + end + end + end + + helpers do + def authorize_read_feature_flags! + authorize! :read_feature_flag, user_project + end + + def authorize_read_feature_flag! + authorize! :read_feature_flag, feature_flag + end + + def authorize_create_feature_flag! + authorize! :create_feature_flag, user_project + end + + def authorize_update_feature_flag! + authorize! :update_feature_flag, feature_flag + end + + def authorize_destroy_feature_flag! + authorize! :destroy_feature_flag, feature_flag + end + + def present_entity(result) + present result, + with: ::API::Entities::FeatureFlag, + feature_flags_new_version_enabled: feature_flags_new_version_enabled? + end + + def ensure_post_version_2_flags_enabled! + unless feature_flags_new_version_enabled? + render_api_error!('Version 2 flags are not enabled for this project', :unprocessable_entity) + end + end + + def feature_flag + @feature_flag ||= if feature_flags_new_version_enabled? + user_project.operations_feature_flags.find_by_name!(params[:feature_flag_name]) + else + user_project.operations_feature_flags.legacy_flag.find_by_name!(params[:feature_flag_name]) + end + end + + def new_version_flag_present? + user_project.operations_feature_flags.new_version_flag.find_by_name(params[:name]).present? + end + + def feature_flags_new_version_enabled? + Feature.enabled?(:feature_flags_new_version, user_project, default_enabled: true) + end + + def rename_key(hash, old_key, new_key) + hash[new_key] = hash.delete(old_key) if hash.key?(old_key) + hash + end + + def update_value(hash, key) + hash[key] = yield(hash[key]) if hash.key?(key) + hash + end + end + end +end diff --git a/lib/api/feature_flags_user_lists.rb b/lib/api/feature_flags_user_lists.rb new file mode 100644 index 00000000000..67aee3993f1 --- /dev/null +++ b/lib/api/feature_flags_user_lists.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +module API + class FeatureFlagsUserLists < Grape::API::Instance + include PaginationParams + + error_formatter :json, -> (message, _backtrace, _options, _env, _original_exception) { + message.is_a?(String) ? { message: message }.to_json : message.to_json + } + + before do + authorize_admin_feature_flags_user_lists! + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource 'projects/:id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + resource :feature_flags_user_lists do + desc 'Get all feature flags user lists of a project' do + detail 'This feature was introduced in GitLab 12.10' + success ::API::Entities::FeatureFlag::UserList + end + params do + use :pagination + end + get do + present paginate(user_project.operations_feature_flags_user_lists), + with: ::API::Entities::FeatureFlag::UserList + end + + desc 'Create a feature flags user list for a project' do + detail 'This feature was introduced in GitLab 12.10' + success ::API::Entities::FeatureFlag::UserList + end + params do + requires :name, type: String, desc: 'The name of the list' + requires :user_xids, type: String, desc: 'A comma separated list of external user ids' + end + post do + list = user_project.operations_feature_flags_user_lists.create(declared_params) + + if list.save + present list, with: ::API::Entities::FeatureFlag::UserList + else + render_api_error!(list.errors.full_messages, :bad_request) + end + end + end + + params do + requires :iid, type: String, desc: 'The internal id of the user list' + end + resource 'feature_flags_user_lists/:iid' do + desc 'Get a single feature flag user list belonging to a project' do + detail 'This feature was introduced in GitLab 12.10' + success ::API::Entities::FeatureFlag::UserList + end + get do + present user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid]), + with: ::API::Entities::FeatureFlag::UserList + end + + desc 'Update a feature flag user list' do + detail 'This feature was introduced in GitLab 12.10' + success ::API::Entities::FeatureFlag::UserList + end + params do + optional :name, type: String, desc: 'The name of the list' + optional :user_xids, type: String, desc: 'A comma separated list of external user ids' + end + put do + list = user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid]) + + if list.update(declared_params(include_missing: false)) + present list, with: ::API::Entities::FeatureFlag::UserList + else + render_api_error!(list.errors.full_messages, :bad_request) + end + end + + desc 'Delete a feature flag user list' do + detail 'This feature was introduced in GitLab 12.10' + end + delete do + list = user_project.operations_feature_flags_user_lists.find_by_iid!(params[:iid]) + unless list.destroy + render_api_error!(list.errors.full_messages, :conflict) + end + end + end + end + + helpers do + def authorize_admin_feature_flags_user_lists! + authorize! :admin_feature_flags_user_lists, user_project + end + end + end +end diff --git a/lib/backup/repositories.rb b/lib/backup/repositories.rb index fc5b5e59e07..c8268a14bfe 100644 --- a/lib/backup/repositories.rb +++ b/lib/backup/repositories.rb @@ -46,6 +46,10 @@ module Backup restore_repository(project, Gitlab::GlRepository::DESIGN) end + Snippet.find_each(batch_size: 1000) do |snippet| + restore_repository(snippet, Gitlab::GlRepository::SNIPPET) + end + restore_object_pools end diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml index 568ceceeaa2..ec33020205b 100644 --- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml @@ -9,9 +9,8 @@ code_quality: DOCKER_TLS_CERTDIR: "" CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:0.85.10-gitlab.1" needs: [] - before_script: - - export SOURCE_CODE=$PWD script: + - export SOURCE_CODE=$PWD - | if ! docker info &>/dev/null; then if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then diff --git a/lib/gitlab/graphql/pagination/keyset/order_info.rb b/lib/gitlab/graphql/pagination/keyset/order_info.rb index 577f59911f5..f3ce3a10703 100644 --- a/lib/gitlab/graphql/pagination/keyset/order_info.rb +++ b/lib/gitlab/graphql/pagination/keyset/order_info.rb @@ -95,7 +95,9 @@ module Gitlab elsif ordering_by_similarity?(order_value) ['similarity', order_value.direction, order_value.expr] elsif ordering_by_case?(order_value) - [order_value.expr.case.name.to_s, order_value.direction, order_value.expr] + ['case_order_value', order_value.direction, order_value.expr] + elsif ordering_by_array_position?(order_value) + ['array_position', order_value.direction, order_value.expr] else [order_value.expr.name, order_value.direction, nil] end @@ -106,6 +108,11 @@ module Gitlab order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'lower' end + # determine if ordering using ARRAY_POSITION, eg. "ORDER BY ARRAY_POSITION(Array[4,3,1,2]::smallint, state)" + def ordering_by_array_position?(order_value) + order_value.expr.is_a?(Arel::Nodes::NamedFunction) && order_value.expr&.name&.downcase == 'array_position' + end + # determine if ordering using SIMILARITY scoring based on Gitlab::Database::SimilarityScore def ordering_by_similarity?(order_value) Gitlab::Database::SimilarityScore.order_by_similarity?(order_value) diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index 9668badc757..f16bd7c735b 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -66,7 +66,7 @@ module GoogleApi cluster_options = make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons) - request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(cluster_options) + request_body = Google::Apis::ContainerV1beta1::CreateClusterRequest.new(**cluster_options) service.create_cluster(project_id, zone, request_body, options: user_agent_header) end |