From 546aa0cae4999176bf914a00893f25e177886c53 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 15 Nov 2018 15:28:29 +1300 Subject: Call ClusterPlatformConfigureWorker to re-use code We remove configure_project_service_account and replace ClusterPlatformConfigureWorker as they perform exactly the same piece of work. This also makes GKE cluster creation to be the same as Adding existing cluster - they both now use another worker to execute CreateOrUpdateNamespaceService. --- app/services/clusters/gcp/finalize_creation_service.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 3df43657fa0..9efe8d85dee 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -12,7 +12,8 @@ module Clusters create_gitlab_service_account! configure_kubernetes cluster.save! - configure_project_service_account + + ClusterPlatformConfigureWorker.perform_async(cluster.id) rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e provider.make_errored!("Failed to request to CloudPlatform; #{e.message}") @@ -55,15 +56,6 @@ module Clusters ).execute end - def configure_project_service_account - kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project) - - Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute - end - def authorization_type create_rbac_cluster? ? 'rbac' : 'abac' end -- cgit v1.2.1 From d3866fb48cdf640cd16d48b9825dba2c261a78f3 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 28 Nov 2018 23:31:28 +1300 Subject: Modify service so that it can be re-run If the service fails mid-point, then we should be able to re-run this service. So, detect presence of any previously created Kubernetes resource and update or create accordingly. Fix specs accordingly. In the case of finalize_creation_service_spec.rb, I decided to stub out the async worker rather than maintaining individual stubs for various kubeclient calls for that worker. --- .../clusters/gcp/kubernetes/create_service_account_service.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'app') diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb index dfc4bf7a358..f2d7cc05552 100644 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb @@ -38,8 +38,9 @@ module Clusters def execute ensure_project_namespace_exists if namespace_creator - kubeclient.create_service_account(service_account_resource) - kubeclient.create_secret(service_account_token_resource) + + kubeclient.create_or_update_service_account(service_account_resource) + kubeclient.create_or_update_secret(service_account_token_resource) create_role_or_cluster_role_binding if rbac end @@ -56,9 +57,9 @@ module Clusters def create_role_or_cluster_role_binding if namespace_creator - kubeclient.create_role_binding(role_binding_resource) + kubeclient.create_or_update_role_binding(role_binding_resource) else - kubeclient.create_cluster_role_binding(cluster_role_binding_resource) + kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource) end end -- cgit v1.2.1 From 5bb2814ae6eb45463bb1fa4c5ed5fdd376afa2ca Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 29 Oct 2018 15:28:36 +1300 Subject: Deploy to clusters for a project's groups Look for matching clusters starting from the closest ancestor, then go up the ancestor tree. Then use Ruby to get clusters for each group in order. Not that efficient, considering we will doing up to `NUMBER_OF_ANCESTORS_ALLOWED` number of queries, but it's a finite number Explicitly order query by depth This allows us to control ordering explicitly and also to reverse the order which is useful to allow us to be consistent with Clusters::Cluster.on_environment (EE) which does reverse ordering. Puts querying group clusters behind Feature Flag. Just in case we have issues with performance, we can easily disable this --- app/models/clusters/cluster.rb | 10 ++++++++++ app/models/concerns/deployment_platform.rb | 13 +++++++++++++ 2 files changed, 23 insertions(+) (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 13906c903b9..a3d26d62a22 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -86,6 +86,16 @@ module Clusters scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } + # Returns an ordered list of group clusters order from clusters of closest + # group up to furthest ancestor group + def self.ordered_group_clusters_for_project(project_id) + project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) + hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups) + .base_and_ancestors(depth: :desc) + + hierarchy_groups.flat_map(&:clusters) + end + def status_name if provider provider.status_name diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index e57a3383544..d785e0d505f 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -13,6 +13,7 @@ module DeploymentPlatform def find_deployment_platform(environment) find_cluster_platform_kubernetes(environment: environment) || + find_group_cluster_platform_kubernetes_with_feature_guard(environment: environment) || find_kubernetes_service_integration || build_cluster_and_deployment_platform end @@ -23,6 +24,18 @@ module DeploymentPlatform .last&.platform_kubernetes end + def find_group_cluster_platform_kubernetes_with_feature_guard(environment: nil) + return unless Feature.enabled?(:deploy_group_clusters, default_enabled: true) + + find_group_cluster_platform_kubernetes(environment: environment) + end + + # EE would override this and utilize environment argument + def find_group_cluster_platform_kubernetes(environment: nil) + Clusters::Cluster.enabled.default_environment.ordered_group_clusters_for_project(id) + .last&.platform_kubernetes + end + def find_kubernetes_service_integration services.deployment.reorder(nil).find_by(active: true) end -- cgit v1.2.1 From 703233e1e9ce8572740b7f22ea62b2a59015e564 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 23 Nov 2018 16:28:16 +1300 Subject: Add association project -> kubernetes_namespaces kubernetes_namespaces is not needed for project import/export as it tracks internal state of kubernetes integration --- app/models/project.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/models/project.rb b/app/models/project.rb index 1c5c34111b0..20e3ada9eb8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -238,6 +238,7 @@ class Project < ActiveRecord::Base has_one :cluster_project, class_name: 'Clusters::Project' has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress' + has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace' has_many :prometheus_metrics -- cgit v1.2.1 From 9c5977c821c1958709227a7a5976e1faedc53923 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Sun, 25 Nov 2018 21:26:28 +1300 Subject: Teach Project about #all_clusters This returns a union of the project level clusters and group level clusters associated with this project. --- app/models/clusters/cluster.rb | 1 + app/models/project.rb | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index a3d26d62a22..e85e7a4e8be 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -4,6 +4,7 @@ module Clusters class Cluster < ActiveRecord::Base include Presentable include Gitlab::Utils::StrongMemoize + include FromUnion self.table_name = 'clusters' diff --git a/app/models/project.rb b/app/models/project.rb index 20e3ada9eb8..8f41629e5e5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1061,6 +1061,12 @@ class Project < ActiveRecord::Base path end + def all_clusters + group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } ) + + Clusters::Cluster.from_union([clusters, group_clusters]) + end + def items_for(entity) case entity when 'issue' then -- cgit v1.2.1 From 8419b7dd2b85fbe9216a31ce84d5ecb234a8b90a Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 26 Nov 2018 22:01:18 +1300 Subject: Teach Cluster about #all_projects For project level, it's the project directly associated. For group level, it's the projects under that group. --- app/models/clusters/cluster.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index e85e7a4e8be..54314f69c3d 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -133,6 +133,16 @@ module Clusters !user? end + def all_projects + if project_type? + projects + elsif group_type? + first_group.all_projects + else + Project.none + end + end + def first_project strong_memoize(:first_project) do projects.first -- cgit v1.2.1 From d54791e0942ae774876db22675cde1b54f35109d Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 15 Nov 2018 22:17:41 +1300 Subject: Create k8s namespace for project in group clusters AFAIK the only relevant place is Projects::CreateService, this gets called when user creates a new project, forks a new project and does those things via the api. Also create k8s namespace for new group hierarchy when transferring project between groups Uses new Refresh service to create k8s namespaces - Ensure we use Cluster#cluster_project If a project has multiple clusters (EE), using Project#cluster_project is not guaranteed to return the cluster_project for this cluster. So switch to using Cluster#cluster_project - at this stage a cluster can only have 1 cluster_project. Also, remove rescue so that sidekiq can retry --- app/models/clusters/cluster.rb | 22 ++++++++++++---- app/models/project.rb | 6 +++++ app/services/clusters/refresh_service.rb | 33 ++++++++++++++++++++++++ app/services/projects/create_service.rb | 6 +++++ app/services/projects/transfer_service.rb | 5 ++++ app/workers/all_queues.yml | 1 + app/workers/cluster_platform_configure_worker.rb | 12 +-------- app/workers/cluster_project_configure_worker.rb | 12 +++++++++ 8 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 app/services/clusters/refresh_service.rb create mode 100644 app/workers/cluster_project_configure_worker.rb (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 54314f69c3d..73cae8d3b25 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -87,6 +87,12 @@ module Clusters scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } + scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do + subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id') + + where('NOT EXISTS (?)', subquery) + end + # Returns an ordered list of group clusters order from clusters of closest # group up to furthest ancestor group def self.ordered_group_clusters_for_project(project_id) @@ -161,11 +167,17 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end - def find_or_initialize_kubernetes_namespace(cluster_project) - kubernetes_namespaces.find_or_initialize_by( - project: cluster_project.project, - cluster_project: cluster_project - ) + def find_or_initialize_kubernetes_namespace_for_project(project) + if project_type? + kubernetes_namespaces.find_or_initialize_by( + project: project, + cluster_project: cluster_project + ) + else + kubernetes_namespaces.find_or_initialize_by( + project: project + ) + end end def allow_user_defined_namespace? diff --git a/app/models/project.rb b/app/models/project.rb index 8f41629e5e5..a3580961d42 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -383,6 +383,12 @@ class Project < ActiveRecord::Base .where(project_ci_cd_settings: { group_runners_enabled: true }) end + scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do + subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id') + + where('NOT EXISTS (?)', subquery) + end + enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 } chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600 diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb new file mode 100644 index 00000000000..51859a002c0 --- /dev/null +++ b/app/services/clusters/refresh_service.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Clusters + class RefreshService + def create_or_update_namespaces_for_cluster(cluster) + cluster_namespaces = cluster.kubernetes_namespaces + + # Create all namespaces that are missing for each project + cluster.all_projects.missing_kubernetes_namespace(cluster_namespaces).each do |project| + kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) + + ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end + end + + def create_or_update_namespaces_for_project(project) + project_namespaces = project.kubernetes_namespaces + + # Create all namespaces that are missing for each cluster + project.all_clusters.missing_kubernetes_namespace(project_namespaces).each do |cluster| + kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) + + ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 9e77a3237e3..d03137b63b2 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -96,6 +96,8 @@ module Projects current_user.invalidate_personal_projects_count create_readme if @initialize_with_readme + + configure_group_clusters_for_project end # Refresh the current user's authorizations inline (so they can access the @@ -121,6 +123,10 @@ module Projects Files::CreateService.new(@project, current_user, commit_attrs).execute end + def configure_group_clusters_for_project + ClusterProjectConfigureWorker.perform_async(@project.id) + end + def skip_wiki? !@project.feature_available?(:wiki, current_user) || @skip_wiki end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 9d40ab166ff..9db3fd9cf17 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -54,6 +54,7 @@ module Projects end attempt_transfer_transaction + configure_group_clusters_for_project end # rubocop: enable CodeReuse/ActiveRecord @@ -162,5 +163,9 @@ module Projects @new_namespace.full_path ) end + + def configure_group_clusters_for_project + ClusterProjectConfigureWorker.perform_async(project.id) + end end end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index c0b410472eb..e51da79c6b5 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -29,6 +29,7 @@ - gcp_cluster:wait_for_cluster_creation - gcp_cluster:cluster_wait_for_ingress_ip_address - gcp_cluster:cluster_platform_configure +- gcp_cluster:cluster_project_configure - github_import_advance_stage - github_importer:github_import_import_diff_note diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_platform_configure_worker.rb index 8f3689f0166..7f14f8efa2f 100644 --- a/app/workers/cluster_platform_configure_worker.rb +++ b/app/workers/cluster_platform_configure_worker.rb @@ -6,17 +6,7 @@ class ClusterPlatformConfigureWorker def perform(cluster_id) Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - next unless cluster.cluster_project - - kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace(cluster.cluster_project) - - Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute + Clusters::RefreshService.new.create_or_update_namespaces_for_cluster(cluster) end - - rescue ::Kubeclient::HttpError => err - Rails.logger.error "Failed to create/update Kubernetes namespace for cluster_id: #{cluster_id} with error: #{err.message}" end end diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb new file mode 100644 index 00000000000..1271b26e945 --- /dev/null +++ b/app/workers/cluster_project_configure_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class ClusterProjectConfigureWorker + include ApplicationWorker + include ClusterQueue + + def perform(project_id) + project = Project.find(project_id) + + ::Clusters::RefreshService.new.create_or_update_namespaces_for_project(project) + end +end -- cgit v1.2.1 From f85440e63c2eba453e09a5599f0d3e0491a037f1 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Mon, 3 Dec 2018 10:22:33 +1300 Subject: Various improvements to hierarchy sorting - Rename ordered_group_clusters_for_project -> ancestor_clusters_for_clusterable - Improve name of order option. It makes much more sense to have `hierarchy_order: :asc` and `hierarchy_order: :desc` - Allow ancestor_clusters_for_clusterable for group - Re-use code already present in Project --- app/models/clusters/cluster.rb | 8 ++------ app/models/concerns/deployment_platform.rb | 4 ++-- app/models/namespace.rb | 4 ++-- app/models/project.rb | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 73cae8d3b25..e7d0471813a 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -93,12 +93,8 @@ module Clusters where('NOT EXISTS (?)', subquery) end - # Returns an ordered list of group clusters order from clusters of closest - # group up to furthest ancestor group - def self.ordered_group_clusters_for_project(project_id) - project_groups = ::Group.joins(:projects).where(projects: { id: project_id }) - hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups) - .base_and_ancestors(depth: :desc) + def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) + hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order) hierarchy_groups.flat_map(&:clusters) end diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index d785e0d505f..ad981f4a8a3 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -32,8 +32,8 @@ module DeploymentPlatform # EE would override this and utilize environment argument def find_group_cluster_platform_kubernetes(environment: nil) - Clusters::Cluster.enabled.default_environment.ordered_group_clusters_for_project(id) - .last&.platform_kubernetes + Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self) + .first&.platform_kubernetes end def find_kubernetes_service_integration diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 11b03846f0b..c38310ed62b 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -192,9 +192,9 @@ class Namespace < ActiveRecord::Base # returns all ancestors upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned - def ancestors_upto(top = nil) + def ancestors_upto(top = nil, hierarchy_order: nil) Gitlab::GroupHierarchy.new(self.class.where(id: id)) - .ancestors(upto: top) + .ancestors(upto: top, hierarchy_order: hierarchy_order) end def self_and_ancestors diff --git a/app/models/project.rb b/app/models/project.rb index a3580961d42..602347d4bac 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -552,9 +552,9 @@ class Project < ActiveRecord::Base # returns all ancestor-groups upto but excluding the given namespace # when no namespace is given, all ancestors upto the top are returned - def ancestors_upto(top = nil) + def ancestors_upto(top = nil, hierarchy_order: nil) Gitlab::GroupHierarchy.new(Group.where(id: namespace_id)) - .base_and_ancestors(upto: top) + .base_and_ancestors(upto: top, hierarchy_order: hierarchy_order) end def lfs_enabled? -- cgit v1.2.1 From ebf87fd9c2e1c5bde72f2c08db0fff7e81882cb8 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 29 Nov 2018 13:50:19 +1300 Subject: Unify into :group_clusters feature flag With this MR, group clusters is now functional, so default to enabled. Have a single setting on the root ancestor group to enabled or disable group clusters feature as a whole --- app/controllers/groups/clusters_controller.rb | 8 ++++++-- app/helpers/groups_helper.rb | 2 +- app/models/concerns/deployment_platform.rb | 2 +- app/models/group.rb | 4 ++++ app/models/namespace.rb | 2 +- app/models/project.rb | 2 ++ 6 files changed, 15 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb index 50c44b7a58b..b846fb21266 100644 --- a/app/controllers/groups/clusters_controller.rb +++ b/app/controllers/groups/clusters_controller.rb @@ -3,8 +3,8 @@ class Groups::ClustersController < Clusters::ClustersController include ControllerWithCrossProjectAccessCheck - prepend_before_action :check_group_clusters_feature_flag! prepend_before_action :group + prepend_before_action :check_group_clusters_feature_flag! requires_cross_project_access layout 'group' @@ -20,6 +20,10 @@ class Groups::ClustersController < Clusters::ClustersController end def check_group_clusters_feature_flag! - render_404 unless Feature.enabled?(:group_clusters) + render_404 unless group_clusters_enabled? + end + + def group_clusters_enabled? + group.group_clusters_enabled? end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index e9b9b9b7721..866fc555856 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -140,7 +140,7 @@ module GroupsHelper can?(current_user, "read_group_#{resource}".to_sym, @group) end - if can?(current_user, :read_cluster, @group) && Feature.enabled?(:group_clusters) + if can?(current_user, :read_cluster, @group) && @group.group_clusters_enabled? links << :kubernetes end diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index ad981f4a8a3..0107af5f8ec 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -25,7 +25,7 @@ module DeploymentPlatform end def find_group_cluster_platform_kubernetes_with_feature_guard(environment: nil) - return unless Feature.enabled?(:deploy_group_clusters, default_enabled: true) + return unless group_clusters_enabled? find_group_cluster_platform_kubernetes(environment: environment) end diff --git a/app/models/group.rb b/app/models/group.rb index 02ddc8762af..233747cc2c2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -400,6 +400,10 @@ class Group < Namespace ensure_runners_token! end + def group_clusters_enabled? + Feature.enabled?(:group_clusters, root_ancestor, default_enabled: true) + end + private def update_two_factor_requirement diff --git a/app/models/namespace.rb b/app/models/namespace.rb index c38310ed62b..8865c164b11 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -243,7 +243,7 @@ class Namespace < ActiveRecord::Base end def root_ancestor - ancestors.reorder(nil).find_by(parent_id: nil) + self_and_ancestors.reorder(nil).find_by(parent_id: nil) end def subgroup? diff --git a/app/models/project.rb b/app/models/project.rb index 602347d4bac..5a35a6a1a2a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -291,6 +291,8 @@ class Project < ActiveRecord::Base delegate :add_guest, :add_reporter, :add_developer, :add_maintainer, :add_role, to: :team delegate :add_master, to: :team # @deprecated delegate :group_runners_enabled, :group_runners_enabled=, :group_runners_enabled?, to: :ci_cd_settings + delegate :group_clusters_enabled?, to: :group, allow_nil: true + delegate :root_ancestor, to: :namespace, allow_nil: true # Validations validates :creator, presence: true, on: :create -- cgit v1.2.1 From 6c642c087bef3b7925ca5c8cf93078b58a8efe98 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Tue, 4 Dec 2018 00:38:20 +1300 Subject: Eager load clusters to prevent N+1 This also means we need to apply the `current_scope` otherwise this method will return all clusters associated with the groups regardless of any scopes applied to this method --- app/models/clusters/cluster.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index e7d0471813a..c9bd1728dbd 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -94,7 +94,8 @@ module Clusters end def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) - hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order) + hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters) + hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope hierarchy_groups.flat_map(&:clusters) end -- cgit v1.2.1 From 9c140b7d26faaaa939dc3f2461fafc8cc434e47c Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Tue, 4 Dec 2018 23:47:27 +1300 Subject: DRY up refresh service The two pieces of code are identical so use a private method --- app/services/clusters/refresh_service.rb | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) (limited to 'app') diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb index 51859a002c0..c203f495b7c 100644 --- a/app/services/clusters/refresh_service.rb +++ b/app/services/clusters/refresh_service.rb @@ -7,12 +7,7 @@ module Clusters # Create all namespaces that are missing for each project cluster.all_projects.missing_kubernetes_namespace(cluster_namespaces).each do |project| - kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) - - ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute + create_or_update_namespace(cluster, project) end end @@ -21,13 +16,19 @@ module Clusters # Create all namespaces that are missing for each cluster project.all_clusters.missing_kubernetes_namespace(project_namespaces).each do |cluster| - kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) - - ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( - cluster: cluster, - kubernetes_namespace: kubernetes_namespace - ).execute + create_or_update_namespace(cluster, project) end end + + private + + def create_or_update_namespace(cluster, project) + kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) + + ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( + cluster: cluster, + kubernetes_namespace: kubernetes_namespace + ).execute + end end end -- cgit v1.2.1 From ba2d8a3f3483af053eea47f84c158509a91f7012 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 5 Dec 2018 09:09:45 +1300 Subject: Rename to CreateOrUpdateServiceAccountService This reflects how we now create or update --- .../clusters/gcp/finalize_creation_service.rb | 2 +- .../create_or_update_namespace_service.rb | 2 +- .../create_or_update_service_account_service.rb | 102 +++++++++++++++++++++ .../kubernetes/create_service_account_service.rb | 102 --------------------- 4 files changed, 104 insertions(+), 104 deletions(-) create mode 100644 app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb delete mode 100644 app/services/clusters/gcp/kubernetes/create_service_account_service.rb (limited to 'app') diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 9efe8d85dee..e029323774c 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -26,7 +26,7 @@ module Clusters private def create_gitlab_service_account! - Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator( + Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.gitlab_creator( kube_client, rbac: create_rbac_cluster? ).execute diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb index b31426556f6..806f320381d 100644 --- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb @@ -27,7 +27,7 @@ module Clusters end def create_project_service_account - Clusters::Gcp::Kubernetes::CreateServiceAccountService.namespace_creator( + Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator( platform.kubeclient, service_account_name: kubernetes_namespace.service_account_name, service_account_namespace: kubernetes_namespace.namespace, diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb new file mode 100644 index 00000000000..49e766cbf13 --- /dev/null +++ b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module Clusters + module Gcp + module Kubernetes + class CreateOrUpdateServiceAccountService + def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil) + @kubeclient = kubeclient + @service_account_name = service_account_name + @service_account_namespace = service_account_namespace + @token_name = token_name + @rbac = rbac + @namespace_creator = namespace_creator + @role_binding_name = role_binding_name + end + + def self.gitlab_creator(kubeclient, rbac:) + self.new( + kubeclient, + service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME, + service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE, + token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, + rbac: rbac + ) + end + + def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:) + self.new( + kubeclient, + service_account_name: service_account_name, + service_account_namespace: service_account_namespace, + token_name: "#{service_account_namespace}-token", + rbac: rbac, + namespace_creator: true, + role_binding_name: "gitlab-#{service_account_namespace}" + ) + end + + def execute + ensure_project_namespace_exists if namespace_creator + + kubeclient.create_or_update_service_account(service_account_resource) + kubeclient.create_or_update_secret(service_account_token_resource) + create_role_or_cluster_role_binding if rbac + end + + private + + attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name + + def ensure_project_namespace_exists + Gitlab::Kubernetes::Namespace.new( + service_account_namespace, + kubeclient + ).ensure_exists! + end + + def create_role_or_cluster_role_binding + if namespace_creator + kubeclient.create_or_update_role_binding(role_binding_resource) + else + kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource) + end + end + + def service_account_resource + Gitlab::Kubernetes::ServiceAccount.new( + service_account_name, + service_account_namespace + ).generate + end + + def service_account_token_resource + Gitlab::Kubernetes::ServiceAccountToken.new( + token_name, + service_account_name, + service_account_namespace + ).generate + end + + def cluster_role_binding_resource + subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] + + Gitlab::Kubernetes::ClusterRoleBinding.new( + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME, + Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, + subjects + ).generate + end + + def role_binding_resource + Gitlab::Kubernetes::RoleBinding.new( + name: role_binding_name, + role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, + namespace: service_account_namespace, + service_account_name: service_account_name + ).generate + end + end + end + end +end diff --git a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb deleted file mode 100644 index f2d7cc05552..00000000000 --- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Gcp - module Kubernetes - class CreateServiceAccountService - def initialize(kubeclient, service_account_name:, service_account_namespace:, token_name:, rbac:, namespace_creator: false, role_binding_name: nil) - @kubeclient = kubeclient - @service_account_name = service_account_name - @service_account_namespace = service_account_namespace - @token_name = token_name - @rbac = rbac - @namespace_creator = namespace_creator - @role_binding_name = role_binding_name - end - - def self.gitlab_creator(kubeclient, rbac:) - self.new( - kubeclient, - service_account_name: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAME, - service_account_namespace: Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE, - token_name: Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME, - rbac: rbac - ) - end - - def self.namespace_creator(kubeclient, service_account_name:, service_account_namespace:, rbac:) - self.new( - kubeclient, - service_account_name: service_account_name, - service_account_namespace: service_account_namespace, - token_name: "#{service_account_namespace}-token", - rbac: rbac, - namespace_creator: true, - role_binding_name: "gitlab-#{service_account_namespace}" - ) - end - - def execute - ensure_project_namespace_exists if namespace_creator - - kubeclient.create_or_update_service_account(service_account_resource) - kubeclient.create_or_update_secret(service_account_token_resource) - create_role_or_cluster_role_binding if rbac - end - - private - - attr_reader :kubeclient, :service_account_name, :service_account_namespace, :token_name, :rbac, :namespace_creator, :role_binding_name - - def ensure_project_namespace_exists - Gitlab::Kubernetes::Namespace.new( - service_account_namespace, - kubeclient - ).ensure_exists! - end - - def create_role_or_cluster_role_binding - if namespace_creator - kubeclient.create_or_update_role_binding(role_binding_resource) - else - kubeclient.create_or_update_cluster_role_binding(cluster_role_binding_resource) - end - end - - def service_account_resource - Gitlab::Kubernetes::ServiceAccount.new( - service_account_name, - service_account_namespace - ).generate - end - - def service_account_token_resource - Gitlab::Kubernetes::ServiceAccountToken.new( - token_name, - service_account_name, - service_account_namespace - ).generate - end - - def cluster_role_binding_resource - subjects = [{ kind: 'ServiceAccount', name: service_account_name, namespace: service_account_namespace }] - - Gitlab::Kubernetes::ClusterRoleBinding.new( - Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME, - Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME, - subjects - ).generate - end - - def role_binding_resource - Gitlab::Kubernetes::RoleBinding.new( - name: role_binding_name, - role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME, - namespace: service_account_namespace, - service_account_name: service_account_name - ).generate - end - end - end - end -end -- cgit v1.2.1 From e3188eb13e3145e9bd4b123c304e43b18eeb1154 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 5 Dec 2018 11:57:02 +1300 Subject: Shift to class methods for RefreshService As we don't use any instance attributes and we don't seem to have any commonalities between the cluster and the project variant. --- app/services/clusters/refresh_service.rb | 30 ++++++++++++++---------- app/workers/cluster_platform_configure_worker.rb | 2 +- app/workers/cluster_project_configure_worker.rb | 2 +- 3 files changed, 20 insertions(+), 14 deletions(-) (limited to 'app') diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb index c203f495b7c..7c82b98a33f 100644 --- a/app/services/clusters/refresh_service.rb +++ b/app/services/clusters/refresh_service.rb @@ -2,27 +2,31 @@ module Clusters class RefreshService - def create_or_update_namespaces_for_cluster(cluster) - cluster_namespaces = cluster.kubernetes_namespaces - - # Create all namespaces that are missing for each project - cluster.all_projects.missing_kubernetes_namespace(cluster_namespaces).each do |project| + def self.create_or_update_namespaces_for_cluster(cluster) + projects_with_missing_kubernetes_namespaces_for_cluster(cluster).each do |project| create_or_update_namespace(cluster, project) end end - def create_or_update_namespaces_for_project(project) - project_namespaces = project.kubernetes_namespaces - - # Create all namespaces that are missing for each cluster - project.all_clusters.missing_kubernetes_namespace(project_namespaces).each do |cluster| + def self.create_or_update_namespaces_for_project(project) + clusters_with_missing_kubernetes_namespaces_for_project(project).each do |cluster| create_or_update_namespace(cluster, project) end end - private + def self.projects_with_missing_kubernetes_namespaces_for_cluster(cluster) + cluster.all_projects.missing_kubernetes_namespace(cluster.kubernetes_namespaces) + end + + private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster - def create_or_update_namespace(cluster, project) + def self.clusters_with_missing_kubernetes_namespaces_for_project(project) + project.all_clusters.missing_kubernetes_namespace(project.kubernetes_namespaces) + end + + private_class_method :clusters_with_missing_kubernetes_namespaces_for_project + + def self.create_or_update_namespace(cluster, project) kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project) ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new( @@ -30,5 +34,7 @@ module Clusters kubernetes_namespace: kubernetes_namespace ).execute end + + private_class_method :create_or_update_namespace end end diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_platform_configure_worker.rb index 7f14f8efa2f..aa7570caa79 100644 --- a/app/workers/cluster_platform_configure_worker.rb +++ b/app/workers/cluster_platform_configure_worker.rb @@ -6,7 +6,7 @@ class ClusterPlatformConfigureWorker def perform(cluster_id) Clusters::Cluster.find_by_id(cluster_id).try do |cluster| - Clusters::RefreshService.new.create_or_update_namespaces_for_cluster(cluster) + Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster) end end end diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb index 1271b26e945..497e57c0d0b 100644 --- a/app/workers/cluster_project_configure_worker.rb +++ b/app/workers/cluster_project_configure_worker.rb @@ -7,6 +7,6 @@ class ClusterProjectConfigureWorker def perform(project_id) project = Project.find(project_id) - ::Clusters::RefreshService.new.create_or_update_namespaces_for_project(project) + ::Clusters::RefreshService.create_or_update_namespaces_for_project(project) end end -- cgit v1.2.1