Commit 33813f99 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent dc0622db
12.3.0-pre 12.5.0-pre
...@@ -125,19 +125,21 @@ export default { ...@@ -125,19 +125,21 @@ export default {
</div> </div>
<div class="commit-actions flex-row"> <div class="commit-actions flex-row">
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div> <div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
<gl-link <div class="ci-status-link">
v-if="commit.latestPipeline" <gl-link
v-gl-tooltip v-if="commit.latestPipeline"
:href="commit.latestPipeline.detailedStatus.detailsPath" v-gl-tooltip
:title="statusTitle" :href="commit.latestPipeline.detailedStatus.detailsPath"
class="js-commit-pipeline" :title="statusTitle"
> class="js-commit-pipeline"
<ci-icon >
:status="commit.latestPipeline.detailedStatus" <ci-icon
:size="24" :status="commit.latestPipeline.detailedStatus"
:aria-label="statusTitle" :size="24"
/> :aria-label="statusTitle"
</gl-link> />
</gl-link>
</div>
<div class="commit-sha-group d-flex"> <div class="commit-sha-group d-flex">
<div class="label label-monospace monospace"> <div class="label label-monospace monospace">
{{ showCommitId }} {{ showCommitId }}
......
.memory-graph-container { .memory-graph-container {
svg { svg {
background: $white-light; background: $white-light;
cursor: pointer; border: 1px solid $gray-200;
&:hover {
box-shadow: 0 0 4px $gray-darkest inset;
}
} }
path { path {
......
...@@ -142,6 +142,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -142,6 +142,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope, :environment_scope,
:managed, :managed,
:base_domain, :base_domain,
:management_project_id,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:api_url, :api_url,
:token, :token,
...@@ -155,6 +156,7 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -155,6 +156,7 @@ class Clusters::ClustersController < Clusters::BaseController
:environment_scope, :environment_scope,
:managed, :managed,
:base_domain, :base_domain,
:management_project_id,
platform_kubernetes_attributes: [ platform_kubernetes_attributes: [
:namespace :namespace
] ]
......
...@@ -117,6 +117,8 @@ module Clusters ...@@ -117,6 +117,8 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) } scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) }
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc) def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
return [] if clusterable.is_a?(Instance) return [] if clusterable.is_a?(Instance)
......
...@@ -20,7 +20,7 @@ module Clusters ...@@ -20,7 +20,7 @@ module Clusters
.with .with
.recursive(cte.to_arel) .recursive(cte.to_arel)
.from(cte_alias) .from(cte_alias)
.order(DEPTH_COLUMN => :asc) .order(depth_order_clause)
end end
private private
...@@ -40,7 +40,7 @@ module Clusters ...@@ -40,7 +40,7 @@ module Clusters
end end
if clusterable.is_a?(::Project) && include_management_project if clusterable.is_a?(::Project) && include_management_project
cte << management_clusters_query cte << same_namespace_management_clusters_query
end end
cte << base_query cte << base_query
...@@ -49,13 +49,42 @@ module Clusters ...@@ -49,13 +49,42 @@ module Clusters
cte cte
end end
# Returns project-level clusters where the project is the management project
# for the cluster. The management project has to be in the same namespace /
# group as the cluster's project.
#
# Support for management project in sub-groups is planned in
# https://gitlab.com/gitlab-org/gitlab/issues/34650
#
# NB: group_parent_id is un-used but we still need to match the same number of
# columns as other queries in the CTE.
def same_namespace_management_clusters_query
clusterable.management_clusters
.project_type
.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"])
.for_project_namespace(clusterable.namespace_id)
end
# Management clusters should be first in the hierarchy so we use 0 for the # Management clusters should be first in the hierarchy so we use 0 for the
# depth column. # depth column.
# #
# group_parent_id is un-used but we still need to match the same number of # Only applicable if the clusterable is a project (most especially when
# columns as other queries in the CTE. # requesting project.deployment_platform).
def management_clusters_query def depth_order_clause
clusterable.management_clusters.select([clusters_star, 'NULL AS group_parent_id', "0 AS #{DEPTH_COLUMN}"]) return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) && include_management_project
order = <<~SQL
(CASE clusters.management_project_id
WHEN :project_id THEN 0
ELSE #{DEPTH_COLUMN}
END) ASC
SQL
values = {
project_id: clusterable.id
}
model.sanitize_sql_array([Arel.sql(order), values])
end end
def group_clusters_base_query def group_clusters_base_query
......
...@@ -12,7 +12,7 @@ module DeploymentPlatform ...@@ -12,7 +12,7 @@ module DeploymentPlatform
private private
def cluster_management_project_enabled? def cluster_management_project_enabled?
Feature.enabled?(:cluster_management_project, default_enabled: true) Feature.enabled?(:cluster_management_project, self)
end end
def find_deployment_platform(environment) def find_deployment_platform(environment)
......
...@@ -9,7 +9,55 @@ module Clusters ...@@ -9,7 +9,55 @@ module Clusters
end end
def execute(cluster) def execute(cluster)
cluster.update(params) if validate_params(cluster)
cluster.update(params)
else
false
end
end
private
def can_admin_pipeline_for_project?(project)
Ability.allowed?(current_user, :admin_pipeline, project)
end
def validate_params(cluster)
if params[:management_project_id]
management_project = management_project_scope(cluster).find_by_id(params[:management_project_id])
unless management_project
cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
return false
end
unless can_admin_pipeline_for_project?(management_project)
# Use same message as not found to prevent enumeration
cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
return false
end
end
true
end
def management_project_scope(cluster)
return ::Project.all if cluster.instance_type?
group =
if cluster.group_type?
cluster.first_group
elsif cluster.project_type?
cluster.first_project&.namespace
end
# Prevent users from selecting nested projects until
# https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved
include_subgroups = cluster.group_type?
::GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: include_subgroups }).execute
end end
end end
end end
---
title: Remove pointer cursor from MemoryUsage chart on MR widget deployment
merge_request: 18599
author:
type: fixed
---
title: Adds ability to set management project for cluster via API
merge_request: 18429
author:
type: added
---
title: Fixed admin geo collapsed sidebar fly out not showing
merge_request: 19012
author:
type: fixed
...@@ -53,6 +53,16 @@ Example response: ...@@ -53,6 +53,16 @@ Example response:
"api_url":"https://104.197.68.152", "api_url":"https://104.197.68.152",
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
},
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
} }
}, },
{ {
...@@ -111,6 +121,16 @@ Example response: ...@@ -111,6 +121,16 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
}, },
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
},
"group": "group":
{ {
"id":26, "id":26,
...@@ -135,6 +155,7 @@ Parameters: ...@@ -135,6 +155,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `name` | String | yes | The name of the cluster | | `name` | String | yes | The name of the cluster |
| `domain` | String | no | The [base domain](../user/group/clusters/index.md#base-domain) of the cluster | | `domain` | String | no | The [base domain](../user/group/clusters/index.md#base-domain) of the cluster |
| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
| `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true | | `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true |
| `managed` | Boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true | | `managed` | Boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true |
| `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API | | `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API |
...@@ -178,6 +199,7 @@ Example response: ...@@ -178,6 +199,7 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
}, },
"management_project":null,
"group": "group":
{ {
"id":26, "id":26,
...@@ -248,6 +270,16 @@ Example response: ...@@ -248,6 +270,16 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":null "ca_cert":null
}, },
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
},
"group": "group":
{ {
"id":26, "id":26,
......
...@@ -54,6 +54,16 @@ Example response: ...@@ -54,6 +54,16 @@ Example response:
"namespace":"cluster-1-namespace", "namespace":"cluster-1-namespace",
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
},
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
} }
}, },
{ {
...@@ -113,6 +123,16 @@ Example response: ...@@ -113,6 +123,16 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
}, },
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
},
"project": "project":
{ {
"id":26, "id":26,
...@@ -205,6 +225,7 @@ Example response: ...@@ -205,6 +225,7 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----" "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
}, },
"management_project":null,
"project": "project":
{ {
"id":26, "id":26,
...@@ -253,6 +274,7 @@ Parameters: ...@@ -253,6 +274,7 @@ Parameters:
| `cluster_id` | integer | yes | The ID of the cluster | | `cluster_id` | integer | yes | The ID of the cluster |
| `name` | String | no | The name of the cluster | | `name` | String | no | The name of the cluster |
| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster | | `domain` | String | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
| `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API | | `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API |
| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes | | `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate | | `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
...@@ -300,6 +322,16 @@ Example response: ...@@ -300,6 +322,16 @@ Example response:
"authorization_type":"rbac", "authorization_type":"rbac",
"ca_cert":null "ca_cert":null
}, },
"management_project":
{
"id":2,
"description":null,
"name":"project2",
"name_with_namespace":"John Doe8 / project2",
"path":"project2",
"path_with_namespace":"namespace2/project2",
"created_at":"2019-10-11T02:55:54.138Z"
},
"project": "project":
{ {
"id":26, "id":26,
......
...@@ -4,7 +4,7 @@ CAUTION: **Warning:** ...@@ -4,7 +4,7 @@ CAUTION: **Warning:**
This is an _alpha_ feature, and it is subject to change at any time without This is an _alpha_ feature, and it is subject to change at any time without
prior notice. prior notice.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/17866) in GitLab 12.4 > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32810) in GitLab 12.5
A project can be designated as the management project for a cluster. A project can be designated as the management project for a cluster.
A management project can be used to run deployment jobs with A management project can be used to run deployment jobs with
...@@ -22,6 +22,14 @@ This can be useful for: ...@@ -22,6 +22,14 @@ This can be useful for:
Only the management project will receive `cluster-admin` privileges. All Only the management project will receive `cluster-admin` privileges. All
other projects will continue to receive [namespace scoped `edit` level privileges](../project/clusters/index.md#rbac-cluster-resources). other projects will continue to receive [namespace scoped `edit` level privileges](../project/clusters/index.md#rbac-cluster-resources).
Management projects are restricted to the following:
- For project-level clusters, the management project must in the same
namespace (or descendants) as the cluster's project.
- For group-level clusters, the management project must in the same
group (or descendants) as as the cluster's group.
- For instance-level clusters, there are no such restrictions.
## Usage ## Usage
### Selecting a cluster management project ### Selecting a cluster management project
...@@ -87,9 +95,9 @@ configure production cluster: ...@@ -87,9 +95,9 @@ configure production cluster:
name: production name: production
``` ```
## Disabling this feature ## Enabling this feature
This feature is enabled by default. To disable this feature, disable the This feature is disabled by default. To enable this feature, enable the
feature flag `:cluster_management_project`. feature flag `:cluster_management_project`.
To check if the feature flag is enabled on your GitLab instance, To check if the feature flag is enabled on your GitLab instance,
......
...@@ -1791,6 +1791,7 @@ module API ...@@ -1791,6 +1791,7 @@ module API
expose :user, using: Entities::UserBasic expose :user, using: Entities::UserBasic
expose :platform_kubernetes, using: Entities::Platform::Kubernetes expose :platform_kubernetes, using: Entities::Platform::Kubernetes
expose :provider_gcp, using: Entities::Provider::Gcp expose :provider_gcp, using: Entities::Provider::Gcp
expose :management_project, using: Entities::ProjectIdentity
end end
class ClusterProject < Cluster class ClusterProject < Cluster
......
...@@ -84,6 +84,7 @@ module API ...@@ -84,6 +84,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID' requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name' optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain' optional :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
optional :token, type: String, desc: 'Token to authenticate against Kubernetes' optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
......
...@@ -140,7 +140,8 @@ module API ...@@ -140,7 +140,8 @@ module API
{ {
repository: repository.gitaly_repository, repository: repository.gitaly_repository,
address: Gitlab::GitalyClient.address(project.repository_storage), address: Gitlab::GitalyClient.address(project.repository_storage),
token: Gitlab::GitalyClient.token(project.repository_storage) token: Gitlab::GitalyClient.token(project.repository_storage),
features: Feature::Gitaly.server_feature_flags
} }
end end
end end
......
...@@ -88,6 +88,7 @@ module API ...@@ -88,6 +88,7 @@ module API
requires :cluster_id, type: Integer, desc: 'The cluster ID' requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name' optional :name, type: String, desc: 'Cluster name'
optional :domain, type: String, desc: 'Cluster base domain' optional :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
optional :token, type: String, desc: 'Token to authenticate against Kubernetes' optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
......
...@@ -12701,6 +12701,9 @@ msgstr "" ...@@ -12701,6 +12701,9 @@ msgstr ""
msgid "Project details" msgid "Project details"
msgstr "" msgstr ""
msgid "Project does not exist or you don't have permission to perform this action"
msgstr ""
msgid "Project export could not be deleted." msgid "Project export could not be deleted."
msgstr "" msgstr ""
......
...@@ -62,19 +62,23 @@ exports[`Repository last commit component renders commit widget 1`] = ` ...@@ -62,19 +62,23 @@ exports[`Repository last commit component renders commit widget 1`] = `
> >
<!----> <!---->
<gllink-stub <div
class="js-commit-pipeline" class="ci-status-link"
data-original-title="Commit: failed"
href="https://test.com/pipeline"
title=""
> >
<ciicon-stub <gllink-stub
aria-label="Commit: failed" class="js-commit-pipeline"
cssclasses="" data-original-title="Commit: failed"
size="24" href="https://test.com/pipeline"
status="[object Object]" title=""
/> >
</gllink-stub> <ciicon-stub
aria-label="Commit: failed"
cssclasses=""
size="24"
status="[object Object]"
/>
</gllink-stub>
</div>
<div <div
class="commit-sha-group d-flex" class="commit-sha-group d-flex"
...@@ -165,19 +169,23 @@ exports[`Repository last commit component renders the signature HTML as returned ...@@ -165,19 +169,23 @@ exports[`Repository last commit component renders the signature HTML as returned
</button> </button>
</div> </div>
<gllink-stub <div
class="js-commit-pipeline" class="ci-status-link"
data-original-title="Commit: failed"
href="https://test.com/pipeline"
title=""
> >
<ciicon-stub <gllink-stub
aria-label="Commit: failed" class="js-commit-pipeline"
cssclasses="" data-original-title="Commit: failed"
size="24" href="https://test.com/pipeline"
status="[object Object]" title=""
/> >
</gllink-stub> <ciicon-stub
aria-label="Commit: failed"
cssclasses=""
size="24"
status="[object Object]"
/>
</gllink-stub>
</div>
<div <div
class="commit-sha-group d-flex" class="commit-sha-group d-flex"
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191017045817_schedule_fix_gitlab_com_pages_access_level.rb') require Rails.root.join('db', 'post_migrate', '20191017045817_schedule_fix_gitlab_com_pages_access_level.rb')
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
...@@ -63,7 +65,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -63,7 +65,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :redis } let(:data_store) { :redis }
before do before do
build_trace_chunk.send(:unsafe_set_data!, 'Sample data in redis') build_trace_chunk.send(:unsafe_set_data!, +'Sample data in redis')
end end
it { is_expected.to eq('Sample data in redis') } it { is_expected.to eq('Sample data in redis') }
...@@ -71,7 +73,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -71,7 +73,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is database' do context 'when data_store is database' do
let(:data_store) { :database } let(:data_store) { :database }
let(:raw_data) { 'Sample data in database' } let(:raw_data) { +'Sample data in database' }
it { is_expected.to eq('Sample data in database') } it { is_expected.to eq('Sample data in database') }
end end
...@@ -80,7 +82,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -80,7 +82,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :fog } let(:data_store) { :fog }
before do before do
build_trace_chunk.send(:unsafe_set_data!, 'Sample data in fog') build_trace_chunk.send(:unsafe_set_data!, +'Sample data in fog')
end end
it { is_expected.to eq('Sample data in fog') } it { is_expected.to eq('Sample data in fog') }
...@@ -90,7 +92,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -90,7 +92,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
describe '#append' do describe '#append' do
subject { build_trace_chunk.append(new_data, offset) } subject { build_trace_chunk.append(new_data, offset) }
let(:new_data) { 'Sample new data' } let(:new_data) { +'Sample new data' }
let(:offset) { 0 } let(:offset) { 0 }
let(:merged_data) { data + new_data.to_s } let(:merged_data) { data + new_data.to_s }
...@@ -143,7 +145,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -143,7 +145,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when new_data is empty' do context 'when new_data is empty' do
let(:new_data) { '' } let(:new_data) { +'' }
it 'does not append' do it 'does not append' do
subject subject
...@@ -172,7 +174,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -172,7 +174,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do shared_examples_for 'Scheduling sidekiq worker to flush data to persist store' do
context 'when new data fulfilled chunk size' do context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE } let(:new_data) { +'a' * described_class::CHUNK_SIZE }
it 'schedules trace chunk flush worker' do it 'schedules trace chunk flush worker' do
expect(Ci::BuildTraceChunkFlushWorker).to receive(:perform_async).once expect(Ci::BuildTraceChunkFlushWorker).to receive(:perform_async).once
...@@ -194,7 +196,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -194,7 +196,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
shared_examples_for 'Scheduling no sidekiq worker' do shared_examples_for 'Scheduling no sidekiq worker' do
context 'when new data fulfilled chunk size' do context 'when new data fulfilled chunk size' do
let(:new_data) { 'a' * described_class::CHUNK_SIZE } let(:new_data) { +'a' * described_class::CHUNK_SIZE }
it 'does not schedule trace chunk flush worker' do it 'does not schedule trace chunk flush worker' do
expect(Ci::BuildTraceChunkFlushWorker).not_to receive(:perform_async) expect(Ci::BuildTraceChunkFlushWorker).not_to receive(:perform_async)
...@@ -219,7 +221,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -219,7 +221,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :redis } let(:data_store) { :redis }
context 'when there are no data' do context 'when there are no data' do
let(:data) { '' } let(:data) { +'' }
it 'has no data' do it 'has no data' do
expect(build_trace_chunk.data).to be_empty expect(build_trace_chunk.data).to be_empty
...@@ -230,7 +232,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -230,7 +232,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when there are some data' do context 'when there are some data' do
let(:data) { 'Sample data in redis' } let(:data) { +'Sample data in redis' }
before do before do
build_trace_chunk.send(:unsafe_set_data!, data) build_trace_chunk.send(:unsafe_set_data!, data)
...@@ -249,7 +251,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -249,7 +251,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :database } let(:data_store) { :database }
context 'when there are no data' do context 'when there are no data' do
let(:data) { '' } let(:data) { +'' }
it 'has no data' do it 'has no data' do
expect(build_trace_chunk.data).to be_empty expect(build_trace_chunk.data).to be_empty
...@@ -260,7 +262,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -260,7 +262,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when there are some data' do context 'when there are some data' do
let(:raw_data) { 'Sample data in database' } let(:raw_data) { +'Sample data in database' }
let(:data) { raw_data } let(:data) { raw_data }
it 'has data' do it 'has data' do
...@@ -276,7 +278,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -276,7 +278,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :fog } let(:data_store) { :fog }
context 'when there are no data' do context 'when there are no data' do
let(:data) { '' } let(:data) { +'' }
it 'has no data' do it 'has no data' do
expect(build_trace_chunk.data).to be_empty expect(build_trace_chunk.data).to be_empty
...@@ -287,7 +289,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -287,7 +289,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when there are some data' do context 'when there are some data' do
let(:data) { 'Sample data in fog' } let(:data) { +'Sample data in fog' }
before do before do
build_trace_chunk.send(:unsafe_set_data!, data) build_trace_chunk.send(:unsafe_set_data!, data)
...@@ -332,7 +334,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -332,7 +334,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is redis' do context 'when data_store is redis' do
let(:data_store) { :redis } let(:data_store) { :redis }
let(:data) { 'Sample data in redis' } let(:data) { +'Sample data in redis' }
before do before do
build_trace_chunk.send(:unsafe_set_data!, data) build_trace_chunk.send(:unsafe_set_data!, data)
...@@ -343,7 +345,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -343,7 +345,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is database' do context 'when data_store is database' do
let(:data_store) { :database } let(:data_store) { :database }
let(:raw_data) { 'Sample data in database' } let(:raw_data) { +'Sample data in database' }
let(:data) { raw_data } let(:data) { raw_data }
it_behaves_like 'truncates' it_behaves_like 'truncates'
...@@ -351,7 +353,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -351,7 +353,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
context 'when data_store is fog' do context 'when data_store is fog' do
let(:data_store) { :fog } let(:data_store) { :fog }
let(:data) { 'Sample data in fog' } let(:data) { +'Sample data in fog' }
before do before do
build_trace_chunk.send(:unsafe_set_data!, data) build_trace_chunk.send(:unsafe_set_data!, data)
...@@ -368,7 +370,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -368,7 +370,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :redis } let(:data_store) { :redis }
context 'when data exists' do context 'when data exists' do
let(:data) { 'Sample data in redis' } let(:data) { +'Sample data in redis' }
before do before do
build_trace_chunk.send(:unsafe_set_data!, data) build_trace_chunk.send(:unsafe_set_data!, data)
...@@ -386,7 +388,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -386,7 +388,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :database } let(:data_store) { :database }
context 'when data exists' do context 'when data exists' do
let(:raw_data) { 'Sample data in database' } let(:raw_data) { +'Sample data in database' }
let(:data) { raw_data } let(:data) { raw_data }
it { is_expected.to eq(data.bytesize) } it { is_expected.to eq(data.bytesize) }
...@@ -401,7 +403,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -401,7 +403,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:data_store) { :fog } let(:data_store) { :fog }
context 'when data exists' do context 'when data exists' do
let(:data) { 'Sample data in fog' } let(:data) { +'Sample data in fog' }
let(:key) { "tmp/builds/#{build.id}/chunks/#{chunk_index}.log" } let(:key) { "tmp/builds/#{build.id}/chunks/#{chunk_index}.log" }
before do before do
...@@ -443,7 +445,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -443,7 +445,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when data size reached CHUNK_SIZE' do context 'when data size reached CHUNK_SIZE' do
let(:data) { 'a' * described_class::CHUNK_SIZE } let(:data) { +'a' * described_class::CHUNK_SIZE }
it 'persists the data' do it 'persists the data' do
expect(build_trace_chunk.redis?).to be_truthy expect(build_trace_chunk.redis?).to be_truthy
...@@ -463,7 +465,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -463,7 +465,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when data size has not reached CHUNK_SIZE' do context 'when data size has not reached CHUNK_SIZE' do
let(:data) { 'Sample data in redis' } let(:data) { +'Sample data in redis' }
it 'does not persist the data and the orignal data is intact' do it 'does not persist the data and the orignal data is intact' do
expect { subject }.to raise_error(described_class::FailedToPersistDataError) expect { subject }.to raise_error(described_class::FailedToPersistDataError)
...@@ -492,7 +494,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -492,7 +494,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when data size reached CHUNK_SIZE' do context 'when data size reached CHUNK_SIZE' do
let(:data) { 'a' * described_class::CHUNK_SIZE } let(:data) { +'a' * described_class::CHUNK_SIZE }
it 'persists the data' do it 'persists the data' do
expect(build_trace_chunk.database?).to be_truthy expect(build_trace_chunk.database?).to be_truthy
...@@ -512,7 +514,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -512,7 +514,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when data size has not reached CHUNK_SIZE' do context 'when data size has not reached CHUNK_SIZE' do
let(:data) { 'Sample data in database' } let(:data) { +'Sample data in database' }
it 'does not persist the data and the orignal data is intact' do it 'does not persist the data and the orignal data is intact' do
expect { subject }.to raise_error(described_class::FailedToPersistDataError) expect { subject }.to raise_error(described_class::FailedToPersistDataError)
...@@ -561,7 +563,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -561,7 +563,7 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
end end
context 'when data size has not reached CHUNK_SIZE' do context 'when data size has not reached CHUNK_SIZE' do
let(:data) { 'Sample data in fog' } let(:data) { +'Sample data in fog' }
it 'does not raise error' do it 'does not raise error' do
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
......
...@@ -152,6 +152,16 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -152,6 +152,16 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end end
end end
describe '.for_project_namespace' do
subject { described_class.for_project_namespace(namespace_id) }
let!(:cluster) { create(:cluster, :project) }
let!(:another_cluster) { create(:cluster, :project) }
let(:namespace_id) { cluster.first_project.namespace_id }
it { is_expected.to contain_exactly(cluster) }
end
describe 'validations' do describe 'validations' do
subject { cluster.valid? } subject { cluster.valid? }
......
...@@ -42,6 +42,28 @@ describe Clusters::ClustersHierarchy do ...@@ -42,6 +42,28 @@ describe Clusters::ClustersHierarchy do
it 'returns clusters for project' do it 'returns clusters for project' do
expect(base_and_ancestors(cluster.project)).to eq([cluster]) expect(base_and_ancestors(cluster.project)).to eq([cluster])
end end
context 'cluster has management project' do
let(:management_project) { create(:project, namespace: cluster.first_project.namespace) }
before do
cluster.update!(management_project: management_project)
end
context 'management_project is in same namespace as cluster' do
it 'returns cluster for management_project' do
expect(base_and_ancestors(management_project)).to eq([cluster])
end
end
context 'management_project is in a different namespace from cluster' do
let(:management_project) { create(:project) }
it 'returns nothing' do
expect(base_and_ancestors(management_project)).to be_empty
end
end
end
end end
context 'cluster has management project' do context 'cluster has management project' do
...@@ -50,16 +72,12 @@ describe Clusters::ClustersHierarchy do ...@@ -50,16 +72,12 @@ describe Clusters::ClustersHierarchy do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let(:management_project) { create(:project) } let(:management_project) { create(:project, group: group) }
it 'returns clusters for management_project' do it 'returns clusters for management_project' do
expect(base_and_ancestors(management_project)).to eq([group_cluster]) expect(base_and_ancestors(management_project)).to eq([group_cluster])
end end
it 'returns nothing if include_management_project is false' do
expect(base_and_ancestors(management_project, include_management_project: false)).to be_empty
end
it 'returns clusters for project' do it 'returns clusters for project' do
expect(base_and_ancestors(project)).to eq([project_cluster, group_cluster]) expect(base_and_ancestors(project)).to eq([project_cluster, group_cluster])
end end
...@@ -70,17 +88,21 @@ describe Clusters::ClustersHierarchy do ...@@ -70,17 +88,21 @@ describe Clusters::ClustersHierarchy do
end end
context 'project in nested group with clusters at some levels' do context 'project in nested group with clusters at some levels' do
let!(:child) { create(:cluster, :group, groups: [child_group], management_project: management_project) } let!(:child) { create(:cluster, :group, groups: [child_group]) }
let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group]) } let!(:ancestor) { create(:cluster, :group, groups: [ancestor_group], management_project: management_project) }
let(:ancestor_group) { create(:group) } let(:ancestor_group) { create(:group) }
let(:parent_group) { create(:group, parent: ancestor_group) } let(:parent_group) { create(:group, parent: ancestor_group) }
let(:child_group) { create(:group, parent: parent_group) } let(:child_group) { create(:group, parent: parent_group) }
let(:project) { create(:project, group: child_group) } let(:project) { create(:project, group: child_group) }
let(:management_project) { create(:project) } let(:management_project) { create(:project, group: child_group) }
it 'returns clusters for management_project' do
expect(base_and_ancestors(management_project)).to eq([ancestor, child])
end
it 'returns clusters for management_project' do it 'returns clusters for management_project' do
expect(base_and_ancestors(management_project)).to eq([child]) expect(base_and_ancestors(management_project, include_management_project: false)).to eq([child, ancestor])
end end
it 'returns clusters for project' do it 'returns clusters for project' do
......
...@@ -13,7 +13,11 @@ describe DeploymentPlatform do ...@@ -13,7 +13,11 @@ describe DeploymentPlatform do
end end
context 'when project is the cluster\'s management project ' do context 'when project is the cluster\'s management project ' do
let!(:cluster_with_management_project) { create(:cluster, :provided_by_user, management_project: project) } let(:another_project) { create(:project, namespace: project.namespace) }
let!(:cluster_with_management_project) do
create(:cluster, :provided_by_user, projects: [another_project], management_project: project)
end
context 'cluster_management_project feature is enabled' do context 'cluster_management_project feature is enabled' do
it 'returns the cluster with management project' do it 'returns the cluster with management project' do
...@@ -66,7 +70,11 @@ describe DeploymentPlatform do ...@@ -66,7 +70,11 @@ describe DeploymentPlatform do
end end
context 'when project is the cluster\'s management project ' do context 'when project is the cluster\'s management project ' do
let!(:cluster_with_management_project) { create(:cluster, :provided_by_user, management_project: project) } let(:another_project) { create(:project, namespace: project.namespace) }
let!(:cluster_with_management_project) do
create(:cluster, :provided_by_user, projects: [another_project], management_project: project)
end
context 'cluster_management_project feature is enabled' do context 'cluster_management_project feature is enabled' do
it 'returns the cluster with management project' do it 'returns the cluster with management project' do
......
# frozen_string_literals: true # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Shard do describe Shard do
......
...@@ -286,12 +286,15 @@ describe API::GroupClusters do ...@@ -286,12 +286,15 @@ describe API::GroupClusters do
let(:update_params) do let(:update_params) do
{ {
domain: domain, domain: domain,
platform_kubernetes_attributes: platform_kubernetes_attributes platform_kubernetes_attributes: platform_kubernetes_attributes,
management_project_id: management_project_id
} }
end end
let(:domain) { 'new-domain.com' } let(:domain) { 'new-domain.com' }
let(:platform_kubernetes_attributes) { {} } let(:platform_kubernetes_attributes) { {} }
let(:management_project) { create(:project, group: group) }
let(:management_project_id) { management_project.id }
let(:cluster) do let(:cluster) do
create(:cluster, :group, :provided_by_gcp, create(:cluster, :group, :provided_by_gcp,
...@@ -308,6 +311,8 @@ describe API::GroupClusters do ...@@ -308,6 +311,8 @@ describe API::GroupClusters do
context 'authorized user' do context 'authorized user' do
before do before do
management_project.add_maintainer(current_user)
put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params
cluster.reload cluster.reload
...@@ -320,6 +325,7 @@ describe API::GroupClusters do ...@@ -320,6 +325,7 @@ describe API::GroupClusters do
it 'updates cluster attributes' do it 'updates cluster attributes' do
expect(cluster.domain).to eq('new-domain.com') expect(cluster.domain).to eq('new-domain.com')
expect(cluster.management_project).to eq(management_project)
end end
end end
...@@ -332,6 +338,7 @@ describe API::GroupClusters do ...@@ -332,6 +338,7 @@ describe API::GroupClusters do
it 'does not update cluster attributes' do it 'does not update cluster attributes' do
expect(cluster.domain).to eq('old-domain.com') expect(cluster.domain).to eq('old-domain.com')
expect(cluster.management_project).to be_nil
end end
it 'returns validation errors' do it 'returns validation errors' do
...@@ -339,6 +346,18 @@ describe API::GroupClusters do ...@@ -339,6 +346,18 @@ describe API::GroupClusters do
end end
end end
context 'current user does not have access to management_project_id' do
let(:management_project_id) { create(:project).id }
it 'responds with 400' do
expect(response).to have_gitlab_http_status(400)
end
it 'returns validation errors' do
expect(json_response['message']['management_project_id'].first).to match('don\'t have permission')
end
end
context 'with a GCP cluster' do context 'with a GCP cluster' do
context 'when user tries to change GCP specific fields' do context 'when user tries to change GCP specific fields' do
let(:platform_kubernetes_attributes) do let(:platform_kubernetes_attributes) do
......
...@@ -316,6 +316,7 @@ describe API::Internal::Base do ...@@ -316,6 +316,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true')
expect(user.reload.last_activity_on).to eql(Date.today) expect(user.reload.last_activity_on).to eql(Date.today)
end end
end end
...@@ -335,6 +336,7 @@ describe API::Internal::Base do ...@@ -335,6 +336,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true')
expect(user.reload.last_activity_on).to be_nil expect(user.reload.last_activity_on).to be_nil
end end
end end
...@@ -576,6 +578,7 @@ describe API::Internal::Base do ...@@ -576,6 +578,7 @@ describe API::Internal::Base do
expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path) expect(json_response["gitaly"]["repository"]["relative_path"]).to eq(project.repository.gitaly_repository.relative_path)
expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage)) expect(json_response["gitaly"]["address"]).to eq(Gitlab::GitalyClient.address(project.repository_storage))
expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage)) expect(json_response["gitaly"]["token"]).to eq(Gitlab::GitalyClient.token(project.repository_storage))
expect(json_response["gitaly"]["features"]).to eq('gitaly-feature-get-all-lfs-pointers-go' => 'true', 'gitaly-feature-inforef-uploadpack-cache' => 'true')
end end
end end
......
...@@ -281,11 +281,14 @@ describe API::ProjectClusters do ...@@ -281,11 +281,14 @@ describe API::ProjectClusters do
let(:api_url) { 'https://kubernetes.example.com' } let(:api_url) { 'https://kubernetes.example.com' }
let(:namespace) { 'new-namespace' } let(:namespace) { 'new-namespace' }
let(:platform_kubernetes_attributes) { { namespace: namespace } } let(:platform_kubernetes_attributes) { { namespace: namespace } }
let(:management_project) { create(:project, namespace: project.namespace) }
let(:management_project_id) { management_project.id }
let(:update_params) do let(:update_params) do
{ {
domain: 'new-domain.com', domain: 'new-domain.com',
platform_kubernetes_attributes: platform_kubernetes_attributes platform_kubernetes_attributes: platform_kubernetes_attributes,
management_project_id: management_project_id
} }
end end
...@@ -310,6 +313,8 @@ describe API::ProjectClusters do ...@@ -310,6 +313,8 @@ describe API::ProjectClusters do
context 'authorized user' do context 'authorized user' do
before do before do
management_project.add_maintainer(current_user)
put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params put api("/projects/#{project.id}/clusters/#{cluster.id}", current_user), params: update_params
cluster.reload cluster.reload
...@@ -323,6 +328,7 @@ describe API::ProjectClusters do ...@@ -323,6 +328,7 @@ describe API::ProjectClusters do
it 'updates cluster attributes' do it 'updates cluster attributes' do
expect(cluster.domain).to eq('new-domain.com') expect(cluster.domain).to eq('new-domain.com')
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace') expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
expect(cluster.management_project).to eq(management_project)
end end
end end
...@@ -336,6 +342,7 @@ describe API::ProjectClusters do ...@@ -336,6 +342,7 @@ describe API::ProjectClusters do
it 'does not update cluster attributes' do it 'does not update cluster attributes' do
expect(cluster.domain).not_to eq('new_domain.com') expect(cluster.domain).not_to eq('new_domain.com')
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace') expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
expect(cluster.management_project).not_to eq(management_project)
end end
it 'returns validation errors' do it 'returns validation errors' do
...@@ -343,6 +350,18 @@ describe API::ProjectClusters do ...@@ -343,6 +350,18 @@ describe API::ProjectClusters do
end end
end end
context 'current user does not have access to management_project_id' do
let(:management_project_id) { create(:project).id }
it 'responds with 400' do
expect(response).to have_gitlab_http_status(400)
end
it 'returns validation errors' do
expect(json_response['message']['management_project_id'].first).to match('don\'t have permission')
end
end
context 'with a GCP cluster' do context 'with a GCP cluster' do
context 'when user tries to change GCP specific fields' do context 'when user tries to change GCP specific fields' do
let(:platform_kubernetes_attributes) do let(:platform_kubernetes_attributes) do
......
...@@ -90,5 +90,115 @@ describe Clusters::UpdateService do ...@@ -90,5 +90,115 @@ describe Clusters::UpdateService do
end end
end end
end end
context 'when params includes :management_project_id' do
context 'management_project is non-existent' do
let(:params) do
{ management_project_id: 0 }
end
it 'does not update management_project_id' do
is_expected.to eq(false)
expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action')
cluster.reload
expect(cluster.management_project_id).to be_nil
end
end
shared_examples 'setting a management project' do
context 'user is authorized to adminster manangement_project' do
before do
management_project.add_maintainer(cluster.user)
end
let(:params) do
{ management_project_id: management_project.id }
end
it 'updates management_project_id' do
is_expected.to eq(true)
expect(cluster.management_project).to eq(management_project)
end
end
context 'user is not authorized to adminster manangement_project' do
let(:params) do
{ management_project_id: management_project.id }
end
it 'does not update management_project_id' do
is_expected.to eq(false)
expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action')
cluster.reload
expect(cluster.management_project_id).to be_nil
end
end
end
context 'project cluster' do
include_examples 'setting a management project' do
let(:management_project) { create(:project, namespace: cluster.first_project.namespace) }
end
context 'manangement_project is outside of the namespace scope' do
before do
management_project.update(group: create(:group))
end
let(:params) do
{ management_project_id: management_project.id }
end
it 'does not update management_project_id' do
is_expected.to eq(false)
expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action')
cluster.reload
expect(cluster.management_project_id).to be_nil
end
end
end
context 'group cluster' do
let(:cluster) { create(:cluster, :group) }
include_examples 'setting a management project' do
let(:management_project) { create(:project, group: cluster.first_group) }
end
context 'manangement_project is outside of the namespace scope' do
before do
management_project.update(group: create(:group))
end
let(:params) do
{ management_project_id: management_project.id }
end
it 'does not update management_project_id' do
is_expected.to eq(false)
expect(cluster.errors[:management_project_id]).to include('Project does not exist or you don\'t have permission to perform this action')
cluster.reload
expect(cluster.management_project_id).to be_nil
end
end
end
context 'instance cluster' do
let(:cluster) { create(:cluster, :instance) }
include_examples 'setting a management project' do
let(:management_project) { create(:project) }
end
end
end
end end
end end
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Sidekiq::Cron::Job do describe Sidekiq::Cron::Job do
......
# frozen_string_literal: true
module SmimeHelper module SmimeHelper
include OpenSSL include OpenSSL
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment