diff options
author | Marin Jankovski <marin@gitlab.com> | 2019-04-23 17:05:26 +0000 |
---|---|---|
committer | Marin Jankovski <marin@gitlab.com> | 2019-04-23 17:05:26 +0000 |
commit | 8907ef5116565cf6e6a01f0e9734faacda324585 (patch) | |
tree | 595a6b5bfca7afdef9bd0787e0685289006f97e2 | |
parent | 8a802d1c6b7ee660fbf701b9758ccff85f065e68 (diff) | |
parent | 46eed98e81ec4c7e6058d916e703a2d749d66436 (diff) | |
download | gitlab-ce-8907ef5116565cf6e6a01f0e9734faacda324585.tar.gz |
Merge branch '11-10-stable-patch-1' into '11-10-stable'
Prepare 11.10.1 release
See merge request gitlab-org/gitlab-ce!27570
41 files changed, 620 insertions, 242 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 7aa332e4163..2b17ffd5042 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.33.0 +1.34.0 diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 2b4ddd7afbc..ed794779ff2 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import Dashboard from './components/dashboard.vue'; -export default () => { +export default (props = {}) => { const el = document.getElementById('prometheus-graphs'); if (el && el.dataset) { @@ -15,6 +15,7 @@ export default () => { ...el.dataset, hasMetrics: parseBoolean(el.dataset.hasMetrics), showTimeWindowDropdown: gon.features.metricsTimeWindow, + ...props, }, }); }, diff --git a/app/assets/javascripts/monitoring/stores/monitoring_store.js b/app/assets/javascripts/monitoring/stores/monitoring_store.js index 9761fe168be..013fb0d4540 100644 --- a/app/assets/javascripts/monitoring/stores/monitoring_store.js +++ b/app/assets/javascripts/monitoring/stores/monitoring_store.js @@ -45,14 +45,13 @@ function removeTimeSeriesNoData(queries) { // ] function groupQueriesByChartInfo(metrics) { const metricsByChart = metrics.reduce((accumulator, metric) => { - const { id, queries, ...chart } = metric; + const { queries, ...chart } = metric; + const metricId = chart.id ? chart.id.toString() : null; const chartKey = `${chart.title}|${chart.y_label}`; accumulator[chartKey] = accumulator[chartKey] || { ...chart, queries: [] }; - queries.forEach(queryAttrs => - accumulator[chartKey].queries.push({ metricId: id.toString(), ...queryAttrs }), - ); + queries.forEach(queryAttrs => accumulator[chartKey].queries.push({ metricId, ...queryAttrs })); return accumulator; }, {}); diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index e82756e4643..edaf07063ec 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -12,6 +12,9 @@ class Clusters::ClustersController < Clusters::BaseController before_action :authorize_update_cluster!, only: [:update] before_action :authorize_admin_cluster!, only: [:destroy] before_action :update_applications_status, only: [:cluster_status] + before_action only: [:show] do + push_frontend_feature_flag(:metrics_time_window) + end helper_method :token_in_session diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 64c88505a16..fa9dda2ab31 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -53,6 +53,7 @@ class IssuableFinder assignee_username author_id author_username + label_name milestone_title my_reaction_emoji search diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb index 7c82b98a33f..76ad8dd0fb0 100644 --- a/app/services/clusters/refresh_service.rb +++ b/app/services/clusters/refresh_service.rb @@ -21,7 +21,11 @@ module Clusters private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster def self.clusters_with_missing_kubernetes_namespaces_for_project(project) - project.all_clusters.missing_kubernetes_namespace(project.kubernetes_namespaces) + if Feature.enabled?(:ci_preparing_state, default_enabled: true) + project.clusters.missing_kubernetes_namespace(project.kubernetes_namespaces) + else + project.all_clusters.missing_kubernetes_namespace(project.kubernetes_namespaces) + end end private_class_method :clusters_with_missing_kubernetes_namespaces_for_project diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 2c95ac6dbb3..4bf1d8702af 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -77,7 +77,7 @@ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago') - #js-related-merge-requests{ data: { endpoint: api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid), project_namespace: @project.namespace.path, project_path: @project.path } } + #js-related-merge-requests{ data: { endpoint: expose_url(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid)), project_namespace: @project.namespace.path, project_path: @project.path } } - if can?(current_user, :download_code, @project) #related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } } diff --git a/app/workers/cluster_configure_worker.rb b/app/workers/cluster_configure_worker.rb index b984dee5b21..22681157b62 100644 --- a/app/workers/cluster_configure_worker.rb +++ b/app/workers/cluster_configure_worker.rb @@ -5,10 +5,10 @@ class ClusterConfigureWorker include ClusterQueue def perform(cluster_id) - return if Feature.enabled?(:ci_preparing_state, default_enabled: true) - Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster) + if cluster.project_type? || Feature.disabled?(:ci_preparing_state, default_enabled: true) + Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster) + end end end end diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb index d7bea69a01c..497e57c0d0b 100644 --- a/app/workers/cluster_project_configure_worker.rb +++ b/app/workers/cluster_project_configure_worker.rb @@ -5,8 +5,6 @@ class ClusterProjectConfigureWorker include ClusterQueue def perform(project_id) - return if Feature.enabled?(:ci_preparing_state, default_enabled: true) - project = Project.find(project_id) ::Clusters::RefreshService.create_or_update_namespaces_for_project(project) diff --git a/changelogs/unreleased/60500-disable-jit-kubernetes-resource-creation-for-project-level-clusters.yml b/changelogs/unreleased/60500-disable-jit-kubernetes-resource-creation-for-project-level-clusters.yml new file mode 100644 index 00000000000..df6e6ea4be3 --- /dev/null +++ b/changelogs/unreleased/60500-disable-jit-kubernetes-resource-creation-for-project-level-clusters.yml @@ -0,0 +1,5 @@ +--- +title: Disable just-in-time Kubernetes resource creation for project level clusters +merge_request: 27352 +author: +type: changed diff --git a/changelogs/unreleased/60569-timeline-entry-label-link-is-not-applying-the-filter-on-issues.yml b/changelogs/unreleased/60569-timeline-entry-label-link-is-not-applying-the-filter-on-issues.yml new file mode 100644 index 00000000000..5319373ec4b --- /dev/null +++ b/changelogs/unreleased/60569-timeline-entry-label-link-is-not-applying-the-filter-on-issues.yml @@ -0,0 +1,5 @@ +--- +title: Fix filtering of labels from system note link +merge_request: 27507 +author: +type: fixed diff --git a/changelogs/unreleased/jc-upgrade-gitaly-1-34-0.yml b/changelogs/unreleased/jc-upgrade-gitaly-1-34-0.yml new file mode 100644 index 00000000000..c1669a484aa --- /dev/null +++ b/changelogs/unreleased/jc-upgrade-gitaly-1-34-0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade Gitaly to 1.34.0 +merge_request: 27494 +author: +type: fixed diff --git a/changelogs/unreleased/sh-backport-list-commits-by-oid-rugged.yml b/changelogs/unreleased/sh-backport-list-commits-by-oid-rugged.yml new file mode 100644 index 00000000000..eb8774d652f --- /dev/null +++ b/changelogs/unreleased/sh-backport-list-commits-by-oid-rugged.yml @@ -0,0 +1,5 @@ +--- +title: Bring back Rugged implementation of ListCommitsByOid +merge_request: 27441 +author: +type: performance diff --git a/changelogs/unreleased/sh-bump-ruby-required-version-check.yml b/changelogs/unreleased/sh-bump-ruby-required-version-check.yml new file mode 100644 index 00000000000..b5b6eb87650 --- /dev/null +++ b/changelogs/unreleased/sh-bump-ruby-required-version-check.yml @@ -0,0 +1,5 @@ +--- +title: Bump required Ruby version check to 2.5.3 +merge_request: 27495 +author: +type: other diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md index 2de751c9e62..6a03ab910fc 100644 --- a/doc/ci/merge_request_pipelines/index.md +++ b/doc/ci/merge_request_pipelines/index.md @@ -69,7 +69,7 @@ when a merge request was created or updated. For example: ## Combined ref pipelines **[PREMIUM]** -> [GitLab Premium](https://about.gitlab.com/pricing/) 11.10. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10. It's possible for your source and target branches to diverge, which can result in the scenario that source branch's pipeline was green, the target's pipeline was green, diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 592fdfd2873..2832bf80ccf 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -137,12 +137,21 @@ The output will be: ![Output custom variable](img/custom_variable_output.png) -CAUTION: **Important:** -Be aware that variables are not masked, and their values can be shown -in the job logs if explicitly asked to do so. If your project is public or -internal, you can set the pipelines private from your [project's Pipelines -settings](../../user/project/pipelines/settings.md#visibility-of-pipelines). -Follow the discussion in issue [#13784][ce-13784] for masking the variables. +### Masked Variables + +By default, variables will be created as masked variables. +This means that the value of the variable will be hidden in job logs, +though it must match certain requirements to do so: + +- The value must be a single line. +- The value must not have escape characters. +- The value must not use variables. +- The value must not have any whitespace. +- The value must be at least 8 characters long. + +If the value does not meet the requirements above, then the CI variable will fail to save. +In order to save, either alter the value to meet the masking requirements +or disable `Masked` for the variable. ### Syntax of environment variables in job scripts {: #syntax-of-variables-in-job-scripts} diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md index 27b69ba8278..94d0c9b69a6 100644 --- a/doc/development/gitaly.md +++ b/doc/development/gitaly.md @@ -80,6 +80,8 @@ most commonly-used RPCs can be enabled via feature flags: * `rugged_get_tree_entries` * `rugged_tree_entry` * `rugged_commit_is_ancestor` +* `rugged_commit_tree_entry` +* `rugged_list_commits_by_oid` A convenience Rake task can be used to enable or disable these flags all together. To enable: diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md new file mode 100644 index 00000000000..72e76ac2a84 --- /dev/null +++ b/doc/user/admin_area/settings/external_authorization.md @@ -0,0 +1,112 @@ +# External authorization control + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4216) in +> [GitLab Premium](https://about.gitlab.com/pricing) 10.6. +> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27056) to +> [GitLab Core](https://about.gitlab.com/pricing/) in 11.10. + +In highly controlled environments, it may be necessary for access policy to be +controlled by an external service that permits access based on project +classification and user access. GitLab provides a way to check project +authorization with your own defined service. + +## Overview + +Once the external service is configured and enabled, when a project is accessed, +a request is made to the external service with the user information and project +classification label assigned to the project. When the service replies with a +known response, the result is cached for 6 hours. + +If the external authorization is enabled, GitLab will further block pages and +functionality that render cross-project data. That includes: + +- most pages under Dashboard (Activity, Milestones, Snippets, Assigned merge + requests, Assigned issues, Todos) +- under a specific group (Activity, Contribution analytics, Issues, Issue boards, + Labels, Milestones, Merge requests) +- Global and Group search will be disabled + +This is to prevent performing to many requests at once to the external +authorization service. + +Whenever access is granted or denied this is logged in a logfile called +`external-policy-access-control.log`. +Read more about logs GitLab keeps in the [omnibus documentation][omnibus-log-docs]. + +## Configuration + +The external authorization service can be enabled by an admin on the GitLab's +admin area under the settings page: + +![Enable external authorization service](img/external_authorization_service_settings.png) + +The available required properties are: + +- **Service URL**: The URL to make authorization requests to. When leaving the + URL blank, cross project features will remain available while still being able + to specify classification labels for projects. +- **External authorization request timeout**: The timeout after which an + authorization request is aborted. When a request times out, access is denied + to the user. +- **Client authentication certificate**: The certificate to use to authenticate + with the external authorization service. +- **Client authentication key**: Private key for the certificate when + authentication is required for the external authorization service, this is + encrypted when stored. +- **Client authentication key password**: Passphrase to use for the private key when authenticating with the external service this is encrypted when stored. +- **Default classification label**: The classification label to use when + requesting authorization if no specific label is defined on the project + +When using TLS Authentication with a self signed certificate, the CA certificate +needs to be trused by the openssl installation. When using GitLab installed using +Omnibus, learn to install a custom CA in the +[omnibus documentation][omnibus-ssl-docs]. Alternatively learn where to install +custom certificates using `openssl version -d`. + +## How it works + +When GitLab requests access, it will send a JSON POST request to the external +service with this body: + +```json +{ + "user_identifier": "jane@acme.org", + "project_classification_label": "project-label", + "user_ldap_dn": "CN=Jane Doe,CN=admin,DC=acme" +} +``` + +The `user_ldap_dn` is optional and is only sent when the user is logged in +through LDAP. + +When the external authorization service responds with a status code 200, the +user is granted access. When the external service responds with a status code +401 or 403, the user is denied access. In any case, the request is cached for 6 hours. + +When denying access, a `reason` can be optionally specified in the JSON body: + +```json +{ + "reason": "You are not allowed access to this project." +} +``` + +Any other status code than 200, 401 or 403 will also deny access to the user, but the +response will not be cached. + +If the service times out (after 500ms), a message "External Policy Server did +not respond" will be displayed. + +## Classification labels + +You can use your own classification label in the project's +**Settings > General > General project settings** page in the "Classification +label" box. When no classification label is specified on a project, the default +label defined in the [global settings](#configuration) will be used. + +The label will be shown on all project pages in the upper right corner. + +![classification label on project page](img/classification_label_on_project_page.png) + +[omnibus-ssl-docs]: https://docs.gitlab.com/omnibus/settings/ssl.html +[omnibus-log-docs]: https://docs.gitlab.com/omnibus/settings/logs.html diff --git a/doc/user/admin_area/settings/img/classification_label_on_project_page.png b/doc/user/admin_area/settings/img/classification_label_on_project_page.png Binary files differnew file mode 100644 index 00000000000..4aedb332cec --- /dev/null +++ b/doc/user/admin_area/settings/img/classification_label_on_project_page.png diff --git a/doc/user/admin_area/settings/img/external_authorization_service_settings.png b/doc/user/admin_area/settings/img/external_authorization_service_settings.png Binary files differnew file mode 100644 index 00000000000..9b8658fd1a1 --- /dev/null +++ b/doc/user/admin_area/settings/img/external_authorization_service_settings.png diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 878d30dddaa..3f673ebb35a 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -565,7 +565,9 @@ service account of the cluster integration. ### Troubleshooting failed deployment jobs GitLab will create a namespace and service account specifically for your -deployment jobs, immediately before the jobs starts. +deployment jobs. On project level clusters, this happens when the cluster +is created. On group level clusters, resources are created immediately +before the deployment job starts. However, sometimes GitLab can not create them. In such instances, your job will fail with the message: diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb index 41135ae62bb..bb2b209e793 100644 --- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb +++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb @@ -6,7 +6,9 @@ module Gitlab module Prerequisite class KubernetesNamespace < Base def unmet? - deployment_cluster.present? && kubernetes_namespace.new_record? + deployment_cluster.present? && + !deployment_cluster.project_type? && + kubernetes_namespace.new_record? end def complete! diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb index f6777dfa0c3..bce4fa14fb4 100644 --- a/lib/gitlab/git/rugged_impl/commit.rb +++ b/lib/gitlab/git/rugged_impl/commit.rb @@ -21,6 +21,17 @@ module Gitlab nil end + # This needs to return an array of Gitlab::Git:Commit objects + # instead of Rugged::Commit objects to ensure upstream models + # operate on a consistent interface. Unlike + # Gitlab::Git::Commit.find, Gitlab::Git::Commit.batch_by_oid + # doesn't attempt to decorate the result. + def rugged_batch_by_oid(repo, oids) + oids.map { |oid| rugged_find(repo, oid) } + .compact + .map { |commit| decorate(repo, commit) } + end + override :find_commit def find_commit(repo, commit_id) if Feature.enabled?(:rugged_find_commit) @@ -29,6 +40,15 @@ module Gitlab super end end + + override :batch_by_oid + def batch_by_oid(repo, oids) + if Feature.enabled?(:rugged_list_commits_by_oid) + rugged_batch_by_oid(repo, oids) + else + super + end + end end extend ::Gitlab::Utils::Override diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb index c0a91f59ab9..e91b0ddcd31 100644 --- a/lib/gitlab/git/rugged_impl/repository.rb +++ b/lib/gitlab/git/rugged_impl/repository.rb @@ -12,7 +12,7 @@ module Gitlab module Repository extend ::Gitlab::Utils::Override - FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor rugged_commit_tree_entry).freeze + FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor rugged_commit_tree_entry rugged_list_commits_by_oid).freeze def alternate_object_directories relative_object_directories.map { |d| File.join(path, d) } diff --git a/lib/gitlab/graphql/authorize.rb b/lib/gitlab/graphql/authorize.rb index f62813db82c..f8d0208e275 100644 --- a/lib/gitlab/graphql/authorize.rb +++ b/lib/gitlab/graphql/authorize.rb @@ -8,7 +8,7 @@ module Gitlab extend ActiveSupport::Concern def self.use(schema_definition) - schema_definition.instrument(:field, Instrumentation.new) + schema_definition.instrument(:field, Instrumentation.new, after_built_ins: true) end end end diff --git a/lib/gitlab/graphql/authorize/authorize_field_service.rb b/lib/gitlab/graphql/authorize/authorize_field_service.rb index 8deff79fc84..03d6aabb0e3 100644 --- a/lib/gitlab/graphql/authorize/authorize_field_service.rb +++ b/lib/gitlab/graphql/authorize/authorize_field_service.rb @@ -15,15 +15,10 @@ module Gitlab def authorized_resolve proc do |parent_typed_object, args, ctx| - resolved_obj = @old_resolve_proc.call(parent_typed_object, args, ctx) - authorizing_obj = authorize_against(parent_typed_object) - checker = build_checker(ctx[:current_user], authorizing_obj) - - if resolved_obj.respond_to?(:then) - resolved_obj.then(&checker) - else - checker.call(resolved_obj) - end + resolved_type = @old_resolve_proc.call(parent_typed_object, args, ctx) + authorizing_object = authorize_against(parent_typed_object, resolved_type) + + filter_allowed(ctx[:current_user], resolved_type, authorizing_object) end end @@ -38,7 +33,7 @@ module Gitlab type = @field.type # When the return type of @field is a collection, find the singular type - if type.get_field('edges') + if @field.connection? type = node_type_for_relay_connection(type) elsif type.list? type = node_type_for_basic_connection(type) @@ -52,43 +47,60 @@ module Gitlab Array.wrap(@field.metadata[:authorize]) end - # If it's a built-in/scalar type, authorize using its parent object. - # nil means authorize using the resolved object - def authorize_against(parent_typed_object) - parent_typed_object.object if built_in_type? && parent_typed_object.respond_to?(:object) + def authorize_against(parent_typed_object, resolved_type) + if built_in_type? + # The field is a built-in/scalar type, or a list of scalars + # authorize using the parent's object + parent_typed_object.object + elsif resolved_type.respond_to?(:object) + # The field is a type representing a single object, we'll authorize + # against the object directly + resolved_type.object + elsif @field.connection? || resolved_type.is_a?(Array) + # The field is a connection or a list of non-built-in types, we'll + # authorize each element when rendering + nil + else + # Resolved type is a single object that might not be loaded yet by + # the batchloader, we'll authorize that + resolved_type + end end - def build_checker(current_user, authorizing_obj) - lambda do |resolved_obj| - # Load the elements if they were not loaded by BatchLoader yet - resolved_obj = resolved_obj.sync if resolved_obj.respond_to?(:sync) - - check = lambda do |object| - authorizations.all? do |ability| - Ability.allowed?(current_user, ability, authorizing_obj || object) - end + def filter_allowed(current_user, resolved_type, authorizing_object) + if authorizing_object + # Authorizing fields representing scalars, or a simple field with an object + resolved_type if allowed_access?(current_user, authorizing_object) + elsif @field.connection? + # A connection with pagination, modify the visible nodes in on the + # connection type in place + resolved_type.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) } + resolved_type + elsif resolved_type.is_a? Array + # A simple list of rendered types each object being an object to authorize + resolved_type.select do |single_object_type| + allowed_access?(current_user, single_object_type.object) end + elsif resolved_type.nil? + # We're not rendering anything, for example when a record was not found + # no need to do anything + else + raise "Can't authorize #{@field}" + end + end - case resolved_obj - when Array, ActiveRecord::Relation - resolved_obj.select(&check) - else - resolved_obj if check.call(resolved_obj) - end + def allowed_access?(current_user, object) + object = object.sync if object.respond_to?(:sync) + + authorizations.all? do |ability| + Ability.allowed?(current_user, ability, object) end end # Returns the singular type for relay connections. # This will be the type class of edges.node def node_type_for_relay_connection(type) - type = type.get_field('edges').type.unwrap.get_field('node')&.type - - if type.nil? - raise Gitlab::Graphql::Errors::ConnectionDefinitionError, - 'Connection Type must conform to the Relay Cursor Connections Specification' - end - - type + type.unwrap.get_field('edges').type.unwrap.get_field('node').type end # Returns the singular type for basic connections, for example `[Types::ProjectType]` diff --git a/lib/gitlab/graphql/connections/keyset_connection.rb b/lib/gitlab/graphql/connections/keyset_connection.rb index 851054c0393..715963a44c1 100644 --- a/lib/gitlab/graphql/connections/keyset_connection.rb +++ b/lib/gitlab/graphql/connections/keyset_connection.rb @@ -22,8 +22,17 @@ module Gitlab end # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def paged_nodes + # These are the nodes that will be loaded into memory for rendering + # So we're ok loading them into memory here as that's bound to happen + # anyway. Having them ready means we can modify the result while + # rendering the fields. + @paged_nodes ||= load_paged_nodes.to_a + end + + private + + def load_paged_nodes if first && last raise Gitlab::Graphql::Errors::ArgumentError.new("Can only provide either `first` or `last`, not both") end @@ -31,12 +40,9 @@ module Gitlab if last sliced_nodes.last(limit_value) else - sliced_nodes.limit(limit_value) + sliced_nodes.limit(limit_value) # rubocop: disable CodeReuse/ActiveRecord end end - # rubocop: enable CodeReuse/ActiveRecord - - private def before_slice if sort_direction == :asc diff --git a/lib/gitlab/graphql/errors.rb b/lib/gitlab/graphql/errors.rb index bcbba72e017..fe74549e322 100644 --- a/lib/gitlab/graphql/errors.rb +++ b/lib/gitlab/graphql/errors.rb @@ -6,7 +6,6 @@ module Gitlab BaseError = Class.new(GraphQL::ExecutionError) ArgumentError = Class.new(BaseError) ResourceNotAvailable = Class.new(BaseError) - ConnectionDefinitionError = Class.new(BaseError) end end end diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb index 60e07718338..53da62df176 100644 --- a/lib/system_check/app/ruby_version_check.rb +++ b/lib/system_check/app/ruby_version_check.rb @@ -7,7 +7,7 @@ module SystemCheck set_check_pass -> { "yes (#{self.current_version})" } def self.required_version - @required_version ||= Gitlab::VersionInfo.new(2, 3, 5) + @required_version ||= Gitlab::VersionInfo.new(2, 5, 3) end def self.current_version diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb index a82b66361ca..f098ec2e167 100644 --- a/spec/controllers/concerns/issuable_collections_spec.rb +++ b/spec/controllers/concerns/issuable_collections_spec.rb @@ -106,51 +106,77 @@ describe IssuableCollections do end describe '#finder_options' do - let(:params) do - { - assignee_id: '1', - assignee_username: 'user1', - author_id: '2', - author_username: 'user2', - authorized_only: 'yes', - confidential: true, - due_date: '2017-01-01', - group_id: '3', - iids: '4', - label_name: ['foo'], - milestone_title: 'bar', - my_reaction_emoji: 'thumbsup', - non_archived: 'true', - project_id: '5', - scope: 'all', - search: 'baz', - sort: 'priority', - state: 'opened', - invalid_param: 'invalid_param' - } - end - - it 'only allows whitelisted params' do + before do allow(controller).to receive(:cookies).and_return({}) allow(controller).to receive(:current_user).and_return(nil) + end + + subject { controller.send(:finder_options).to_h } + + context 'scalar params' do + let(:params) do + { + assignee_id: '1', + assignee_username: 'user1', + author_id: '2', + author_username: 'user2', + authorized_only: 'yes', + confidential: true, + due_date: '2017-01-01', + group_id: '3', + iids: '4', + label_name: 'foo', + milestone_title: 'bar', + my_reaction_emoji: 'thumbsup', + non_archived: 'true', + project_id: '5', + scope: 'all', + search: 'baz', + sort: 'priority', + state: 'opened', + invalid_param: 'invalid_param' + } + end + + it 'only allows whitelisted params' do + is_expected.to include({ + 'assignee_id' => '1', + 'assignee_username' => 'user1', + 'author_id' => '2', + 'author_username' => 'user2', + 'confidential' => true, + 'label_name' => 'foo', + 'milestone_title' => 'bar', + 'my_reaction_emoji' => 'thumbsup', + 'due_date' => '2017-01-01', + 'scope' => 'all', + 'search' => 'baz', + 'sort' => 'priority', + 'state' => 'opened' + }) + + is_expected.not_to include('invalid_param') + end + end + + context 'array params' do + let(:params) do + { + assignee_username: %w[user1 user2], + label_name: %w[label1 label2], + invalid_param: 'invalid_param', + invalid_array: ['param'] + } + end + + it 'only allows whitelisted params' do + is_expected.to include({ + 'label_name' => %w[label1 label2], + 'assignee_username' => %w[user1 user2] + }) - finder_options = controller.send(:finder_options) - - expect(finder_options).to eq(ActionController::Parameters.new({ - 'assignee_id' => '1', - 'assignee_username' => 'user1', - 'author_id' => '2', - 'author_username' => 'user2', - 'confidential' => true, - 'label_name' => ['foo'], - 'milestone_title' => 'bar', - 'my_reaction_emoji' => 'thumbsup', - 'due_date' => '2017-01-01', - 'scope' => 'all', - 'search' => 'baz', - 'sort' => 'priority', - 'state' => 'opened' - }).permit!) + is_expected.not_to include('invalid_param', 'invalid_array') + end end end end diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb index 00e31568a9e..f5eb628a982 100644 --- a/spec/graphql/features/authorization_spec.rb +++ b/spec/graphql/features/authorization_spec.rb @@ -177,6 +177,7 @@ describe 'Gitlab::Graphql::Authorization' do describe 'type authorizations when applied to a relay connection' do let(:query_string) { '{ object() { edges { node { name } } } }' } + let(:second_test_object) { double(name: 'Second thing') } let(:type) do type_factory do |type| @@ -186,22 +187,41 @@ describe 'Gitlab::Graphql::Authorization' do let(:query_type) do query_factory do |query| - query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object] } + query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) { [test_object, second_test_object] } end end subject { result.dig('object', 'edges') } - it 'returns the protected field when user has permission' do + it 'returns only the elements visible to the user' do permit(permission_single) - expect(subject).not_to be_empty + expect(subject.size).to eq 1 expect(subject.first['node']).to eq('name' => test_object.name) end it 'returns nil when user is not authorized' do expect(subject).to be_empty end + + describe 'limiting connections with multiple objects' do + let(:query_type) do + query_factory do |query| + query.field :object, type.connection_type, null: true, resolve: ->(obj, args, ctx) do + [test_object, second_test_object] + end + end + end + + let(:query_string) { '{ object(first: 1) { edges { node { name } } } }' } + + it 'only checks permissions for the first object' do + expect(Ability).to receive(:allowed?).with(user, permission_single, test_object) { true } + expect(Ability).not_to receive(:allowed?).with(user, permission_single, second_test_object) + + expect(subject.size).to eq(1) + end + end end describe 'type authorizations when applied to a basic connection' do @@ -222,28 +242,53 @@ describe 'Gitlab::Graphql::Authorization' do include_examples 'authorization with a single permission' end - describe 'when connections do not follow the correct specification' do - let(:query_string) { '{ object() { edges { node { name }} } }' } + describe 'Authorizations on active record relations' do + let!(:visible_project) { create(:project, :private) } + let!(:other_project) { create(:project, :private) } + let!(:visible_issues) { create_list(:issue, 2, project: visible_project) } + let!(:other_issues) { create_list(:issue, 2, project: other_project) } + let!(:user) { visible_project.owner } - let(:type) do - bad_node = type_factory do |type| - type.graphql_name 'BadNode' - type.field :bad_node, GraphQL::STRING_TYPE, null: true + let(:issue_type) do + type_factory do |type| + type.graphql_name 'FakeIssueType' + type.authorize :read_issue + type.field :id, GraphQL::ID_TYPE, null: false end - + end + let(:project_type) do |type| type_factory do |type| - type.field :edges, [bad_node], null: true + type.graphql_name 'FakeProjectType' + type.field :test_issues, issue_type.connection_type, null: false, resolve: -> (_, _, _) { Issue.where(project: [visible_project, other_project]) } end end - let(:query_type) do query_factory do |query| - query.field :object, type, null: true + query.field :test_project, project_type, null: false, resolve: -> (_, _, _) { visible_project } end end + let(:query_string) do + <<~QRY + { testProject { testIssues(first: 3) { edges { node { id } } } } } + QRY + end + + before do + allow(Ability).to receive(:allowed?).and_call_original + end + + it 'renders the issues the user has access to' do + issue_edges = result['testProject']['testIssues']['edges'] + issue_ids = issue_edges.map { |issue_edge| issue_edge['node']&.fetch('id') } + + expect(issue_edges.size).to eq(visible_issues.size) + expect(issue_ids).to eq(visible_issues.map { |i| i.id.to_s }) + end + + it 'does not check access on fields that will not be rendered' do + expect(Ability).not_to receive(:allowed?).with(user, :read_issue, other_issues.last) - it 'throws an error' do - expect { result }.to raise_error(Gitlab::Graphql::Errors::ConnectionDefinitionError) + result end end @@ -276,6 +321,8 @@ describe 'Gitlab::Graphql::Authorization' do def execute_query(query_type) schema = Class.new(GraphQL::Schema) do use Gitlab::Graphql::Authorize + use Gitlab::Graphql::Connections + query(query_type) end diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb index 74e93b2c4df..05f10fb40f0 100644 --- a/spec/graphql/gitlab_schema_spec.rb +++ b/spec/graphql/gitlab_schema_spec.rb @@ -74,6 +74,6 @@ describe GitlabSchema do end def field_instrumenters - described_class.instrumenters[:field] + described_class.instrumenters[:field] + described_class.instrumenters[:field_after_built_ins] end end diff --git a/spec/javascripts/monitoring/monitoring_store_spec.js b/spec/javascripts/monitoring/monitoring_store_spec.js index d8a980c874d..5bf6937c92e 100644 --- a/spec/javascripts/monitoring/monitoring_store_spec.js +++ b/spec/javascripts/monitoring/monitoring_store_spec.js @@ -32,4 +32,28 @@ describe('MonitoringStore', () => { it('removes the data if all the values from a query are not defined', () => { expect(store.groups[1].metrics[0].queries[0].result.length).toEqual(0); }); + + it('assigns queries a metric id', () => { + expect(store.groups[1].metrics[0].queries[0].metricId).toEqual('100'); + }); + + it('assigns metric id of null if metric has no id', () => { + const noId = MonitoringMock.data.map(group => ({ + ...group, + ...{ + metrics: group.metrics.map(metric => { + const { id, ...metricWithoutId } = metric; + + return metricWithoutId; + }), + }, + })); + store.storeMetrics(noId); + + store.groups.forEach(group => { + group.metrics.forEach(metric => { + expect(metric.queries.every(query => query.metricId === null)).toBe(true); + }); + }); + }); }); diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 62dcd80fad7..e8332b14627 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do let!(:deployment) { create(:deployment, deployable: build) } context 'and a cluster to deploy to' do - let(:cluster) { create(:cluster, projects: [build.project]) } + let(:cluster) { create(:cluster, :group) } before do allow(build.deployment).to receive(:cluster).and_return(cluster) @@ -33,6 +33,12 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do it { is_expected.to be_falsey } end + + context 'and cluster is project type' do + let(:cluster) { create(:cluster, :project) } + + it { is_expected.to be_falsey } + end end context 'and no cluster to deploy to' do @@ -52,7 +58,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do subject { described_class.new(build).complete! } context 'completion is required' do - let(:cluster) { create(:cluster, projects: [build.project]) } + let(:cluster) { create(:cluster, :group) } before do allow(build.deployment).to receive(:cluster).and_return(cluster) diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 507bf222810..25052a79916 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -380,7 +380,32 @@ describe Gitlab::Git::Commit, :seed_helper do end end - describe '#batch_by_oid' do + shared_examples '.batch_by_oid' do + context 'with multiple OIDs' do + let(:oids) { [SeedRepo::Commit::ID, SeedRepo::FirstCommit::ID] } + + it 'returns multiple commits' do + commits = described_class.batch_by_oid(repository, oids) + + expect(commits.count).to eq(2) + expect(commits).to all( be_a(Gitlab::Git::Commit) ) + expect(commits.first.sha).to eq(SeedRepo::Commit::ID) + expect(commits.second.sha).to eq(SeedRepo::FirstCommit::ID) + end + end + + context 'when oids is empty' do + it 'returns empty commits' do + commits = described_class.batch_by_oid(repository, []) + + expect(commits.count).to eq(0) + end + end + end + + describe '.batch_by_oid with Gitaly enabled' do + it_should_behave_like '.batch_by_oid' + context 'when oids is empty' do it 'makes no Gitaly request' do expect(Gitlab::GitalyClient).not_to receive(:call) @@ -390,6 +415,16 @@ describe Gitlab::Git::Commit, :seed_helper do end end + describe '.batch_by_oid with Rugged enabled', :enable_rugged do + it_should_behave_like '.batch_by_oid' + + it 'calls out to the Rugged implementation' do + allow_any_instance_of(Rugged).to receive(:rev_parse).with(SeedRepo::Commit::ID).and_call_original + + described_class.batch_by_oid(repository, [SeedRepo::Commit::ID]) + end + end + shared_examples 'extracting commit signature' do context 'when the commit is signed' do let(:commit_id) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } diff --git a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb index 6114aca0616..95a4eb296fb 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_field_service_spec.rb @@ -5,92 +5,104 @@ require 'spec_helper' # Also see spec/graphql/features/authorization_spec.rb for # integration tests of AuthorizeFieldService describe Gitlab::Graphql::Authorize::AuthorizeFieldService do - describe '#build_checker' do - let(:current_user) { double(:current_user) } - let(:abilities) { [double(:first_ability), double(:last_ability)] } - - context 'when authorizing against the object' do - let(:checker) do - service = described_class.new(double(resolve_proc: proc {})) - allow(service).to receive(:authorizations).and_return(abilities) - service.__send__(:build_checker, current_user, nil) - end + def type(type_authorizations = []) + Class.new(Types::BaseObject) do + graphql_name "TestType" - it 'returns a checker which checks for a single object' do - object = double(:object) + authorize type_authorizations + end + end - abilities.each do |ability| - spy_ability_check_for(ability, object, passed: true) - end + def type_with_field(field_type, field_authorizations = [], resolved_value = "Resolved value") + Class.new(Types::BaseObject) do + graphql_name "TestTypeWithField" + field :test_field, field_type, null: true, authorize: field_authorizations, resolve: -> (_, _, _) { resolved_value} + end + end - expect(checker.call(object)).to eq(object) - end + let(:current_user) { double(:current_user) } + subject(:service) { described_class.new(field) } - it 'returns a checker which checks for all objects' do - objects = [double(:first), double(:last)] + describe "#authorized_resolve" do + let(:presented_object) { double("presented object") } + let(:presented_type) { double("parent type", object: presented_object) } + subject(:resolved) { service.authorized_resolve.call(presented_type, {}, { current_user: current_user }) } - abilities.each do |ability| - objects.each do |object| - spy_ability_check_for(ability, object, passed: true) + context "scalar types" do + shared_examples "checking permissions on the presented object" do + it "checks the abilities on the object being presented and returns the value" do + expected_permissions.each do |permission| + spy_ability_check_for(permission, presented_object, passed: true) end + + expect(resolved).to eq("Resolved value") end - expect(checker.call(objects)).to eq(objects) + it "returns nil if the value wasn't authorized" do + allow(Ability).to receive(:allowed?).and_return false + + expect(resolved).to be_nil + end end - context 'when some objects would not pass the check' do - it 'returns nil when it is single object' do - disallowed = double(:object) + context "when the field is a scalar type" do + let(:field) { type_with_field(GraphQL::STRING_TYPE, :read_field).fields["testField"].to_graphql } + let(:expected_permissions) { [:read_field] } - spy_ability_check_for(abilities.first, disallowed, passed: false) + it_behaves_like "checking permissions on the presented object" + end - expect(checker.call(disallowed)).to be_nil - end + context "when the field is a list of scalar types" do + let(:field) { type_with_field([GraphQL::STRING_TYPE], :read_field).fields["testField"].to_graphql } + let(:expected_permissions) { [:read_field] } - it 'returns only objects which passed when there are more than one' do - allowed = double(:allowed) - disallowed = double(:disallowed) + it_behaves_like "checking permissions on the presented object" + end + end - spy_ability_check_for(abilities.first, disallowed, passed: false) + context "when the field is a specific type" do + let(:custom_type) { type(:read_type) } + let(:object_in_field) { double("presented in field") } + let(:field) { type_with_field(custom_type, :read_field, object_in_field).fields["testField"].to_graphql } - abilities.each do |ability| - spy_ability_check_for(ability, allowed, passed: true) - end + it "checks both field & type permissions" do + spy_ability_check_for(:read_field, object_in_field, passed: true) + spy_ability_check_for(:read_type, object_in_field, passed: true) - expect(checker.call([disallowed, allowed])).to contain_exactly(allowed) - end + expect(resolved).to eq(object_in_field) end - end - context 'when authorizing against another object' do - let(:authorizing_obj) { double(:object) } + it "returns nil if viewing was not allowed" do + spy_ability_check_for(:read_field, object_in_field, passed: false) + spy_ability_check_for(:read_type, object_in_field, passed: true) - let(:checker) do - service = described_class.new(double(resolve_proc: proc {})) - allow(service).to receive(:authorizations).and_return(abilities) - service.__send__(:build_checker, current_user, authorizing_obj) + expect(resolved).to be_nil end - it 'returns a checker which checks for a single object' do - object = double(:object) + context "when the field is a list" do + let(:object_1) { double("presented in field 1") } + let(:object_2) { double("presented in field 2") } + let(:presented_types) { [double(object: object_1), double(object: object_2)] } + let(:field) { type_with_field([custom_type], :read_field, presented_types).fields["testField"].to_graphql } - abilities.each do |ability| - spy_ability_check_for(ability, authorizing_obj, passed: true) + it "checks all permissions" do + allow(Ability).to receive(:allowed?) { true } + + spy_ability_check_for(:read_field, object_1, passed: true) + spy_ability_check_for(:read_type, object_1, passed: true) + spy_ability_check_for(:read_field, object_2, passed: true) + spy_ability_check_for(:read_type, object_2, passed: true) + + expect(resolved).to eq(presented_types) end - expect(checker.call(object)).to eq(object) - end + it "filters out objects that the user cannot see" do + allow(Ability).to receive(:allowed?) { true } - it 'returns a checker which checks for all objects' do - objects = [double(:first), double(:last)] + spy_ability_check_for(:read_type, object_1, passed: false) - abilities.each do |ability| - objects.each do |object| - spy_ability_check_for(ability, authorizing_obj, passed: true) - end + expect(resolved.map(&:object)).to contain_exactly(object_2) end - - expect(checker.call(objects)).to eq(objects) end end end diff --git a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb index 9bcc1e78a78..fefa2881b18 100644 --- a/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb +++ b/spec/lib/gitlab/graphql/connections/keyset_connection_spec.rb @@ -85,6 +85,11 @@ describe Gitlab::Graphql::Connections::KeysetConnection do expect(subject.paged_nodes.size).to eq(3) end + it 'is a loaded memoized array' do + expect(subject.paged_nodes).to be_an(Array) + expect(subject.paged_nodes.object_id).to eq(subject.paged_nodes.object_id) + end + context 'when `first` is passed' do let(:arguments) { { first: 2 } } diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index c2934430821..4f9f916f22e 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -7,8 +7,8 @@ describe 'getting an issue list for a project' do let(:current_user) { create(:user) } let(:issues_data) { graphql_data['project']['issues']['edges'] } let!(:issues) do - create(:issue, project: project, discussion_locked: true) - create(:issue, project: project) + [create(:issue, project: project, discussion_locked: true), + create(:issue, project: project)] end let(:fields) do <<~QUERY @@ -47,6 +47,30 @@ describe 'getting an issue list for a project' do expect(issues_data[1]['node']['discussionLocked']).to eq true end + context 'when limiting the number of results' do + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + "issues(first: 1) { #{fields} }" + ) + end + + it_behaves_like 'a working graphql query' do + before do + post_graphql(query, current_user: current_user) + end + end + + it "is expected to check permissions on the first issue only" do + allow(Ability).to receive(:allowed?).and_call_original + # Newest first, we only want to see the newest checked + expect(Ability).not_to receive(:allowed?).with(current_user, :read_issue, issues.first) + + post_graphql(query, current_user: current_user) + end + end + context 'when the user does not have access to the issue' do it 'returns nil' do project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb index 58ab3c3cf73..9e442ebf4e9 100644 --- a/spec/services/clusters/refresh_service_spec.rb +++ b/spec/services/clusters/refresh_service_spec.rb @@ -93,14 +93,32 @@ describe Clusters::RefreshService do let(:group) { cluster.group } let(:project) { create(:project, group: group) } - include_examples 'creates a kubernetes namespace' + context 'when ci_preparing_state feature flag is enabled' do + include_examples 'does not create a kubernetes namespace' - context 'when project already has kubernetes namespace' do + context 'when project already has kubernetes namespace' do + before do + create(:cluster_kubernetes_namespace, project: project, cluster: cluster) + end + + include_examples 'does not create a kubernetes namespace' + end + end + + context 'when ci_preparing_state feature flag is disabled' do before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) + stub_feature_flags(ci_preparing_state: false) end - include_examples 'does not create a kubernetes namespace' + include_examples 'creates a kubernetes namespace' + + context 'when project already has kubernetes namespace' do + before do + create(:cluster_kubernetes_namespace, project: project, cluster: cluster) + end + + include_examples 'does not create a kubernetes namespace' + end end end end diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb index 83f76809435..bdb8e0e9c84 100644 --- a/spec/workers/cluster_configure_worker_spec.rb +++ b/spec/workers/cluster_configure_worker_spec.rb @@ -10,25 +10,35 @@ describe ClusterConfigureWorker, '#perform' do stub_feature_flags(ci_preparing_state: ci_preparing_state_enabled) end - context 'when group cluster' do - let(:cluster) { create(:cluster, :group, :provided_by_gcp) } - let(:group) { cluster.group } + shared_examples 'configured cluster' do + it 'creates a namespace' do + expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_cluster).with(cluster).once - context 'when group has no projects' do - it 'does not create a namespace' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute) + worker.perform(cluster.id) + end + end - worker.perform(cluster.id) - end + shared_examples 'unconfigured cluster' do + it 'does not create a namespace' do + expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster) + + worker.perform(cluster.id) end + end + + context 'group cluster' do + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + let(:group) { cluster.group } context 'when group has a project' do let!(:project) { create(:project, group: group) } - it 'creates a namespace for the project' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once + it_behaves_like 'configured cluster' + + context 'ci_preparing_state feature is enabled' do + let(:ci_preparing_state_enabled) { true } - worker.perform(cluster.id) + it_behaves_like 'unconfigured cluster' end end @@ -36,32 +46,26 @@ describe ClusterConfigureWorker, '#perform' do let!(:subgroup) { create(:group, parent: group) } let!(:project) { create(:project, group: subgroup) } - it 'creates a namespace for the project' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).once + it_behaves_like 'configured cluster' - worker.perform(cluster.id) + context 'ci_preparing_state feature is enabled' do + let(:ci_preparing_state_enabled) { true } + + it_behaves_like 'unconfigured cluster' end end end context 'when provider type is gcp' do - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - - it 'configures kubernetes platform' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - described_class.new.perform(cluster.id) - end + it_behaves_like 'configured cluster' end context 'when provider type is user' do - let(:cluster) { create(:cluster, :project, :provided_by_user) } + let!(:cluster) { create(:cluster, :project, :provided_by_user) } - it 'configures kubernetes platform' do - expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute) - - described_class.new.perform(cluster.id) - end + it_behaves_like 'configured cluster' end context 'when cluster does not exist' do @@ -71,15 +75,4 @@ describe ClusterConfigureWorker, '#perform' do described_class.new.perform(123) end end - - context 'ci_preparing_state feature is enabled' do - let(:cluster) { create(:cluster) } - let(:ci_preparing_state_enabled) { true } - - it 'does not configure the cluster' do - expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster) - - described_class.new.perform(cluster.id) - end - end end diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb index afdea55adf4..2ac9d0f61b4 100644 --- a/spec/workers/cluster_project_configure_worker_spec.rb +++ b/spec/workers/cluster_project_configure_worker_spec.rb @@ -4,18 +4,11 @@ require 'spec_helper' describe ClusterProjectConfigureWorker, '#perform' do let(:worker) { described_class.new } + let(:cluster) { create(:cluster, :project) } - context 'ci_preparing_state feature is enabled' do - let(:cluster) { create(:cluster) } + it 'configures the cluster' do + expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_project) - before do - stub_feature_flags(ci_preparing_state: true) - end - - it 'does not configure the cluster' do - expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_project) - - described_class.new.perform(cluster.id) - end + described_class.new.perform(cluster.projects.first.id) end end |