summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Baumbauer <cab@cabnetworks.net>2018-11-03 10:13:35 -0700
committerChris Baumbauer <cab@cabnetworks.net>2018-11-03 10:13:35 -0700
commitaa44393e8f6e428bb7159ac8815a143a9e3e1047 (patch)
tree815e91b75359ad33173a667702cecb03cc963535
parentdc078c241765cfea5f49409407b82db7296c132d (diff)
parent4d3ff28a6a0d81f44ccb3eb1602996e5d7c3de1c (diff)
downloadgitlab-ce-aa44393e8f6e428bb7159ac8815a143a9e3e1047.tar.gz
Merge branch 'master' into triggermesh-phase1-knative
-rw-r--r--CHANGELOG.md21
-rw-r--r--app/assets/javascripts/boards/models/issue.js1
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue19
-rw-r--r--app/assets/javascripts/commons/gitlab_ui.js3
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/javascripts/pages/projects/clusters/gcp/new/index.js5
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue38
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js38
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue10
-rw-r--r--app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js9
-rw-r--r--app/controllers/clusters/applications_controller.rb28
-rw-r--r--app/controllers/clusters/base_controller.rb37
-rw-r--r--app/controllers/clusters/clusters_controller.rb218
-rw-r--r--app/controllers/concerns/project_unauthorized.rb10
-rw-r--r--app/controllers/concerns/routable_actions.rb16
-rw-r--r--app/controllers/projects/application_controller.rb3
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb26
-rw-r--r--app/controllers/projects/clusters_controller.rb222
-rw-r--r--app/controllers/projects/commit_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/finders/clusters_finder.rb8
-rw-r--r--app/helpers/clusters_helper.rb5
-rw-r--r--app/helpers/labels_helper.rb2
-rw-r--r--app/mailers/emails/issues.rb14
-rw-r--r--app/mailers/emails/merge_requests.rb14
-rw-r--r--app/mailers/previews/notify_preview.rb20
-rw-r--r--app/models/ci/build.rb3
-rw-r--r--app/models/clusters/cluster.rb8
-rw-r--r--app/models/clusters/kubernetes_namespace.rb35
-rw-r--r--app/models/clusters/platforms/kubernetes.rb36
-rw-r--r--app/models/concerns/token_authenticatable.rb7
-rw-r--r--app/models/concerns/token_authenticatable_strategies/base.rb6
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/key.rb4
-rw-r--r--app/models/merge_request.rb9
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb7
-rw-r--r--app/models/user.rb9
-rw-r--r--app/presenters/clusterable_presenter.rb46
-rw-r--r--app/presenters/clusters/cluster_presenter.rb8
-rw-r--r--app/presenters/project_clusterable_presenter.rb15
-rw-r--r--app/serializers/build_action_entity.rb7
-rw-r--r--app/serializers/job_entity.rb1
-rw-r--r--app/services/clusters/create_service.rb22
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb23
-rw-r--r--app/services/clusters/gcp/kubernetes.rb11
-rw-r--r--app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb54
-rw-r--r--app/services/clusters/gcp/kubernetes/create_service_account_service.rb78
-rw-r--r--app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb8
-rw-r--r--app/services/issuable_base_service.rb8
-rw-r--r--app/services/issues/update_service.rb14
-rw-r--r--app/services/merge_requests/refresh_service.rb11
-rw-r--r--app/services/merge_requests/reload_diffs_service.rb5
-rw-r--r--app/services/merge_requests/update_service.rb14
-rw-r--r--app/services/milestones/destroy_service.rb2
-rw-r--r--app/services/notification_service.rb41
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml (renamed from app/views/projects/clusters/_advanced_settings.html.haml)2
-rw-r--r--app/views/clusters/clusters/_banner.html.haml (renamed from app/views/projects/clusters/_banner.html.haml)0
-rw-r--r--app/views/clusters/clusters/_cluster.html.haml (renamed from app/views/projects/clusters/_cluster.html.haml)4
-rw-r--r--app/views/clusters/clusters/_empty_state.html.haml (renamed from app/views/projects/clusters/_empty_state.html.haml)4
-rw-r--r--app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml (renamed from app/views/projects/clusters/_gcp_signup_offer_banner.html.haml)0
-rw-r--r--app/views/clusters/clusters/_integration_form.html.haml (renamed from app/views/projects/clusters/_integration_form.html.haml)6
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml (renamed from app/views/projects/clusters/_sidebar.html.haml)0
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml (renamed from app/views/projects/clusters/gcp/_form.html.haml)4
-rw-r--r--app/views/clusters/clusters/gcp/_header.html.haml (renamed from app/views/projects/clusters/gcp/_header.html.haml)0
-rw-r--r--app/views/clusters/clusters/gcp/_show.html.haml (renamed from app/views/projects/clusters/gcp/_show.html.haml)2
-rw-r--r--app/views/clusters/clusters/index.html.haml (renamed from app/views/projects/clusters/index.html.haml)0
-rw-r--r--app/views/clusters/clusters/new.html.haml (renamed from app/views/projects/clusters/new.html.haml)8
-rw-r--r--app/views/clusters/clusters/show.html.haml (renamed from app/views/projects/clusters/show.html.haml)23
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml (renamed from app/views/projects/clusters/user/_form.html.haml)4
-rw-r--r--app/views/clusters/clusters/user/_header.html.haml (renamed from app/views/projects/clusters/user/_header.html.haml)0
-rw-r--r--app/views/clusters/clusters/user/_show.html.haml (renamed from app/views/projects/clusters/user/_show.html.haml)2
-rw-r--r--app/views/notify/changed_milestone_issue_email.html.haml3
-rw-r--r--app/views/notify/changed_milestone_issue_email.text.erb1
-rw-r--r--app/views/notify/changed_milestone_merge_request_email.html.haml3
-rw-r--r--app/views/notify/changed_milestone_merge_request_email.text.erb1
-rw-r--r--app/views/notify/removed_milestone_issue_email.html.haml2
-rw-r--r--app/views/notify/removed_milestone_issue_email.text.erb1
-rw-r--r--app/views/notify/removed_milestone_merge_request_email.html.haml2
-rw-r--r--app/views/notify/removed_milestone_merge_request_email.text.erb1
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml4
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/cluster_platform_configure_worker.rb22
-rw-r--r--app/workers/cluster_provision_worker.rb2
-rw-r--r--changelogs/unreleased/28249-add-pagination.yml5
-rw-r--r--changelogs/unreleased/51620-cannot-add-label-to-issue-from-board.yml4
-rw-r--r--changelogs/unreleased/51716-create-kube-namespace.yml5
-rw-r--r--changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml5
-rw-r--r--changelogs/unreleased/ab-45608-stuck-mr-query.yml5
-rw-r--r--changelogs/unreleased/add-scheduled-flag-to-job-entity.yml5
-rw-r--r--changelogs/unreleased/ccr-51520_change_milestone_email.yml5
-rw-r--r--changelogs/unreleased/gl-ui-progress-bar.yml5
-rw-r--r--changelogs/unreleased/kinolaev-master-patch-91872.yml5
-rw-r--r--changelogs/unreleased/remove-ci_enable_scheduled_build-feature-flag.yml5
-rw-r--r--changelogs/unreleased/replace-tooltip-in-markdown-component.yml5
-rw-r--r--changelogs/unreleased/security-kubeclient-ssrf.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-mr-commit-sha-lookup.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-reload-diffs-service.yml5
-rw-r--r--changelogs/unreleased/top_level_clusters_controller.yml6
-rw-r--r--config/initializers/8_metrics.rb6
-rw-r--r--config/initializers/kubeclient.rb21
-rw-r--r--config/routes.rb17
-rw-r--r--config/routes/project.rb15
-rw-r--r--db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb4
-rw-r--r--db/migrate/20181101144347_add_index_for_stuck_mr_query.rb16
-rw-r--r--db/schema.rb3
-rw-r--r--doc/user/project/import/index.md3
-rw-r--r--doc/workflow/notifications.md4
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-rw-r--r--lib/gitlab/cluster/puma_worker_killer_initializer.rb6
-rw-r--r--lib/gitlab/diff/file_collection/base.rb7
-rw-r--r--lib/gitlab/kubernetes/helm.rb1
-rw-r--r--lib/gitlab/kubernetes/helm/base_command.rb14
-rw-r--r--lib/gitlab/kubernetes/helm/pod.rb2
-rw-r--r--lib/gitlab/kubernetes/role_binding.rb11
-rw-r--r--qa/qa/factory/base.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb2
-rw-r--r--qa/spec/factory/base_spec.rb6
-rw-r--r--spec/controllers/projects/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb52
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb1
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb16
-rw-r--r--spec/features/groups/board_sidebar_spec.rb45
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb1
-rw-r--r--spec/features/projects/clusters/user_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/issue.json1
-rw-r--r--spec/helpers/labels_helper_spec.rb25
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js23
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb3
-rw-r--r--spec/models/ci/build_spec.rb12
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb10
-rw-r--r--spec/models/clusters/cluster_spec.rb1
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb85
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb59
-rw-r--r--spec/models/merge_request_diff_spec.rb33
-rw-r--r--spec/models/merge_request_spec.rb30
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb6
-rw-r--r--spec/models/project_spec.rb14
-rw-r--r--spec/presenters/clusterable_presenter_spec.rb17
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb14
-rw-r--r--spec/presenters/project_clusterable_presenter_spec.rb77
-rw-r--r--spec/serializers/build_action_entity_spec.rb4
-rw-r--r--spec/serializers/job_entity_spec.rb1
-rw-r--r--spec/services/ci/process_build_service_spec.rb44
-rw-r--r--spec/services/ci/run_scheduled_build_service_spec.rb4
-rw-r--r--spec/services/clusters/create_service_spec.rb31
-rw-r--r--spec/services/clusters/gcp/finalize_creation_service_spec.rb278
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb115
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb211
-rw-r--r--spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb50
-rw-r--r--spec/services/clusters/update_service_spec.rb7
-rw-r--r--spec/services/issues/update_service_spec.rb48
-rw-r--r--spec/services/merge_requests/reload_diffs_service_spec.rb11
-rw-r--r--spec/services/merge_requests/update_service_spec.rb46
-rw-r--r--spec/services/notification_service_spec.rb160
-rw-r--r--spec/support/shared_examples/helm_generated_script.rb14
-rw-r--r--spec/workers/cluster_platform_configure_worker_spec.rb33
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb9
165 files changed, 2372 insertions, 841 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c99f6ed059..241dcaa7832 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.4.4 (2018-10-30)
+
+### Security (1 change)
+
+- Monkey kubeclient to not follow any redirects.
+
+
## 11.4.3 (2018-10-26)
- No changes.
@@ -250,6 +257,13 @@ entry.
- Check frozen string in style builds. (gfyoung)
+## 11.3.9 (2018-10-31)
+
+### Security (1 change)
+
+- Monkey kubeclient to not follow any redirects.
+
+
## 11.3.8 (2018-10-27)
- No changes.
@@ -555,6 +569,13 @@ entry.
- Creates Vue component for artifacts block on job page.
+## 11.2.8 (2018-10-31)
+
+### Security (1 change)
+
+- Monkey kubeclient to not follow any redirects.
+
+
## 11.2.7 (2018-10-27)
- No changes.
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index bb3b2865934..669630edcab 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -30,6 +30,7 @@ class ListIssue {
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
+ this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
if (obj.project) {
this.project = new IssueProject(obj.project);
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index a2aa3d197e3..82532539c9c 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -2,9 +2,15 @@
import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store';
import pipelinesMixin from '../../pipelines/mixins/pipelines';
+import TablePagination from '../../vue_shared/components/table_pagination.vue';
+import { getParameterByName } from '../../lib/utils/common_utils';
+import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
- mixins: [pipelinesMixin],
+ components: {
+ TablePagination,
+ },
+ mixins: [pipelinesMixin, CIPaginationMixin],
props: {
endpoint: {
type: String,
@@ -35,6 +41,8 @@ export default {
return {
store,
state: store.state,
+ page: getParameterByName('page') || '1',
+ requestData: {},
};
},
@@ -48,11 +56,14 @@ export default {
},
created() {
this.service = new PipelinesService(this.endpoint);
+ this.requestData = { page: this.page };
},
methods: {
successCallback(resp) {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = resp.data.pipelines || resp.data;
+
+ this.store.storePagination(resp.headers);
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
@@ -97,5 +108,11 @@ export default {
:view-type="viewType"
/>
</div>
+
+ <table-pagination
+ v-if="shouldRenderPagination"
+ :change="onChangePage"
+ :page-info="state.pageInfo"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js
index e93e1f5ea2c..82a191d056b 100644
--- a/app/assets/javascripts/commons/gitlab_ui.js
+++ b/app/assets/javascripts/commons/gitlab_ui.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
-import { GlProgressBar, GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
+import { GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui';
-Vue.component('gl-progress-bar', GlProgressBar);
Vue.component('gl-loading-icon', GlLoadingIcon);
Vue.directive('gl-tooltip', GlTooltipDirective);
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 5457604b3b9..c0a76814102 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -59,7 +59,6 @@ export default class LabelsSelect {
$toggleText = $dropdown.find('.dropdown-toggle-text');
namespacePath = $dropdown.data('namespacePath');
projectPath = $dropdown.data('projectPath');
- labelUrl = $dropdown.data('labels');
issueUpdateURL = $dropdown.data('issueUpdate');
selectedLabel = $dropdown.data('selected');
if (selectedLabel != null && !$dropdown.hasClass('js-multiselect')) {
@@ -168,6 +167,7 @@ export default class LabelsSelect {
$dropdown.glDropdown({
showMenuAbove: showMenuAbove,
data: function(term, callback) {
+ labelUrl = $dropdown.attr('data-labels');
axios
.get(labelUrl)
.then(res => {
diff --git a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js b/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js
deleted file mode 100644
index d4f34e32a48..00000000000
--- a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-
-document.addEventListener('DOMContentLoaded', () => {
- initGkeDropdowns();
-});
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index ea526cf1309..fcd8a54c9c1 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -155,14 +155,6 @@ export default {
);
},
- shouldRenderPagination() {
- return (
- !this.isLoading &&
- this.state.pipelines.length &&
- this.state.pageInfo.total > this.state.pageInfo.perPage
- );
- },
-
emptyTabMessage() {
const { scopes } = this.$options;
const possibleScopes = [scopes.pending, scopes.running, scopes.finished];
@@ -232,36 +224,6 @@ export default {
this.setCommonData(resp.data.pipelines);
}
},
- /**
- * Handles URL and query parameter changes.
- * When the user uses the pagination or the tabs,
- * - update URL
- * - Make API request to the server with new parameters
- * - Update the polling function
- * - Update the internal state
- */
- updateContent(parameters) {
- this.updateInternalState(parameters);
-
- // fetch new data
- return this.service
- .getPipelines(this.requestData)
- .then(response => {
- this.isLoading = false;
- this.successCallback(response);
-
- // restart polling
- this.poll.restart({ data: this.requestData });
- })
- .catch(() => {
- this.isLoading = false;
- this.errorCallback();
-
- // restart polling
- this.poll.restart({ data: this.requestData });
- });
- },
-
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 8929b397f6c..85781f548c6 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -23,6 +23,15 @@ export default {
hasMadeRequest: false,
};
},
+ computed: {
+ shouldRenderPagination() {
+ return (
+ !this.isLoading &&
+ this.state.pipelines.length &&
+ this.state.pageInfo.total > this.state.pageInfo.perPage
+ );
+ },
+ },
beforeMount() {
this.poll = new Poll({
resource: this.service,
@@ -65,6 +74,35 @@ export default {
this.poll.stop();
},
methods: {
+ /**
+ * Handles URL and query parameter changes.
+ * When the user uses the pagination or the tabs,
+ * - update URL
+ * - Make API request to the server with new parameters
+ * - Update the polling function
+ * - Update the internal state
+ */
+ updateContent(parameters) {
+ this.updateInternalState(parameters);
+
+ // fetch new data
+ return this.service
+ .getPipelines(this.requestData)
+ .then(response => {
+ this.isLoading = false;
+ this.successCallback(response);
+
+ // restart polling
+ this.poll.restart({ data: this.requestData });
+ })
+ .catch(() => {
+ this.isLoading = false;
+ this.errorCallback();
+
+ // restart polling
+ this.poll.restart({ data: this.requestData });
+ });
+ },
updateTable() {
// Cancel ongoing request
if (this.isMakingRequest) {
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index e74912d628f..b145e5dc5e2 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -1,9 +1,13 @@
<script>
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import tooltip from '../../../vue_shared/directives/tooltip';
+import { GlProgressBar } from '@gitlab-org/gitlab-ui';
export default {
name: 'TimeTrackingComparisonPane',
+ components: {
+ GlProgressBar,
+ },
directives: {
tooltip,
},
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 3ddb39730c4..27e3f314dd3 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,17 +1,17 @@
<script>
import $ from 'jquery';
-import Tooltip from '../../directives/tooltip';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import ToolbarButton from './toolbar_button.vue';
import Icon from '../icon.vue';
export default {
- directives: {
- Tooltip,
- },
components: {
ToolbarButton,
Icon,
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
props: {
previewMarkdown: {
type: Boolean,
@@ -147,7 +147,7 @@ export default {
icon="table"
/>
<button
- v-tooltip
+ v-gl-tooltip
aria-label="Go full screen"
class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
data-container="body"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index 3e89e1c1e75..91d0bbfc21c 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -1,13 +1,13 @@
<script>
-import tooltip from '../../directives/tooltip';
-import icon from '../icon.vue';
+import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
+import Icon from '../icon.vue';
export default {
components: {
- icon,
+ Icon,
},
directives: {
- tooltip,
+ GlTooltip: GlTooltipDirective,
},
props: {
buttonTitle: {
@@ -43,7 +43,7 @@ export default {
<template>
<button
- v-tooltip
+ v-gl-tooltip
:data-md-tag="tag"
:data-md-select="tagSelect"
:data-md-block="tagBlock"
diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
index 67a1632269e..f9e3f3df0cc 100644
--- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
@@ -14,7 +14,14 @@ export default {
onChangePage(page) {
/* URLS parameters are strings, we need to parse to match types */
- this.updateContent({ scope: this.scope, page: Number(page).toString() });
+ const params = {
+ page: Number(page).toString(),
+ };
+
+ if (this.scope) {
+ params.scope = this.scope;
+ }
+ this.updateContent(params);
},
updateInternalState(parameters) {
diff --git a/app/controllers/clusters/applications_controller.rb b/app/controllers/clusters/applications_controller.rb
new file mode 100644
index 00000000000..250f42f3096
--- /dev/null
+++ b/app/controllers/clusters/applications_controller.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class Clusters::ApplicationsController < Clusters::BaseController
+ before_action :cluster
+ before_action :authorize_create_cluster!, only: [:create]
+
+ def create
+ Clusters::Applications::CreateService
+ .new(@cluster, current_user, create_cluster_application_params)
+ .execute(request)
+
+ head :no_content
+ rescue Clusters::Applications::CreateService::InvalidApplicationError
+ render_404
+ rescue StandardError
+ head :bad_request
+ end
+
+ private
+
+ def cluster
+ @cluster ||= clusterable.clusters.find(params[:id]) || render_404
+ end
+
+ def create_cluster_application_params
+ params.permit(:application, :hostname)
+ end
+end
diff --git a/app/controllers/clusters/base_controller.rb b/app/controllers/clusters/base_controller.rb
new file mode 100644
index 00000000000..ef42f7c4074
--- /dev/null
+++ b/app/controllers/clusters/base_controller.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class Clusters::BaseController < ApplicationController
+ include RoutableActions
+
+ skip_before_action :authenticate_user!
+ before_action :authorize_read_cluster!
+
+ helper_method :clusterable
+
+ private
+
+ def cluster
+ @cluster ||= clusterable.clusters.find(params[:id])
+ .present(current_user: current_user)
+ end
+
+ def authorize_update_cluster!
+ access_denied! unless can?(current_user, :update_cluster, cluster)
+ end
+
+ def authorize_admin_cluster!
+ access_denied! unless can?(current_user, :admin_cluster, cluster)
+ end
+
+ def authorize_read_cluster!
+ access_denied! unless can?(current_user, :read_cluster, clusterable)
+ end
+
+ def authorize_create_cluster!
+ access_denied! unless can?(current_user, :create_cluster, clusterable)
+ end
+
+ def clusterable
+ raise NotImplementedError
+ end
+end
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
new file mode 100644
index 00000000000..f6f2060ebb5
--- /dev/null
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+class Clusters::ClustersController < Clusters::BaseController
+ include RoutableActions
+
+ before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
+ before_action :generate_gcp_authorize_url, only: [:new]
+ before_action :validate_gcp_token, only: [:new]
+ before_action :gcp_cluster, only: [:new]
+ before_action :user_cluster, only: [:new]
+ before_action :authorize_create_cluster!, only: [:new]
+ before_action :authorize_update_cluster!, only: [:update]
+ before_action :authorize_admin_cluster!, only: [:destroy]
+ before_action :update_applications_status, only: [:cluster_status]
+
+ helper_method :token_in_session
+
+ STATUS_POLLING_INTERVAL = 10_000
+
+ def index
+ clusters = ClustersFinder.new(clusterable, current_user, :all).execute
+ @clusters = clusters.page(params[:page]).per(20)
+ end
+
+ def new
+ end
+
+ # Overridding ActionController::Metal#status is NOT a good idea
+ def cluster_status
+ respond_to do |format|
+ format.json do
+ Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
+
+ render json: ClusterSerializer
+ .new(current_user: @current_user)
+ .represent_status(@cluster)
+ end
+ end
+ end
+
+ def show
+ end
+
+ def update
+ Clusters::UpdateService
+ .new(current_user, update_params)
+ .execute(cluster)
+
+ if cluster.valid?
+ respond_to do |format|
+ format.json do
+ head :no_content
+ end
+ format.html do
+ flash[:notice] = _('Kubernetes cluster was successfully updated.')
+ redirect_to cluster.show_path
+ end
+ end
+ else
+ respond_to do |format|
+ format.json { head :bad_request }
+ format.html { render :show }
+ end
+ end
+ end
+
+ def destroy
+ if cluster.destroy
+ flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
+ redirect_to clusterable.index_path, status: :found
+ else
+ flash[:notice] = _('Kubernetes cluster integration was not removed.')
+ render :show
+ end
+ end
+
+ def create_gcp
+ @gcp_cluster = ::Clusters::CreateService
+ .new(current_user, create_gcp_cluster_params)
+ .execute(access_token: token_in_session)
+ .present(current_user: current_user)
+
+ if @gcp_cluster.persisted?
+ redirect_to @gcp_cluster.show_path
+ else
+ generate_gcp_authorize_url
+ validate_gcp_token
+ user_cluster
+
+ render :new, locals: { active_tab: 'gcp' }
+ end
+ end
+
+ def create_user
+ @user_cluster = ::Clusters::CreateService
+ .new(current_user, create_user_cluster_params)
+ .execute(access_token: token_in_session)
+ .present(current_user: current_user)
+
+ if @user_cluster.persisted?
+ redirect_to @user_cluster.show_path
+ else
+ generate_gcp_authorize_url
+ validate_gcp_token
+ gcp_cluster
+
+ render :new, locals: { active_tab: 'user' }
+ end
+ end
+
+ private
+
+ def update_params
+ if cluster.managed?
+ params.require(:cluster).permit(
+ :enabled,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :namespace
+ ]
+ )
+ else
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :api_url,
+ :token,
+ :ca_cert,
+ :namespace
+ ]
+ )
+ end
+ end
+
+ def create_gcp_cluster_params
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ provider_gcp_attributes: [
+ :gcp_project_id,
+ :zone,
+ :num_nodes,
+ :machine_type,
+ :legacy_abac
+ ]).merge(
+ provider_type: :gcp,
+ platform_type: :kubernetes,
+ clusterable: clusterable.subject
+ )
+ end
+
+ def create_user_cluster_params
+ params.require(:cluster).permit(
+ :enabled,
+ :name,
+ :environment_scope,
+ platform_kubernetes_attributes: [
+ :namespace,
+ :api_url,
+ :token,
+ :ca_cert,
+ :authorization_type
+ ]).merge(
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: clusterable.subject
+ )
+ end
+
+ def generate_gcp_authorize_url
+ state = generate_session_key_redirect(clusterable.new_path.to_s)
+
+ @authorize_url = GoogleApi::CloudPlatform::Client.new(
+ nil, callback_google_api_auth_url,
+ state: state).authorize_url
+ rescue GoogleApi::Auth::ConfigMissingError
+ # no-op
+ end
+
+ def gcp_cluster
+ @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
+ cluster.build_provider_gcp
+ end
+ end
+
+ def user_cluster
+ @user_cluster = ::Clusters::Cluster.new.tap do |cluster|
+ cluster.build_platform_kubernetes
+ end
+ end
+
+ def validate_gcp_token
+ @valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
+ .validate_token(expires_at_in_session)
+ end
+
+ def token_in_session
+ session[GoogleApi::CloudPlatform::Client.session_key_for_token]
+ end
+
+ def expires_at_in_session
+ @expires_at_in_session ||=
+ session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
+ end
+
+ def generate_session_key_redirect(uri)
+ GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
+ session[key] = uri
+ end
+ end
+
+ def update_applications_status
+ @cluster.applications.each(&:schedule_status_update)
+ end
+end
diff --git a/app/controllers/concerns/project_unauthorized.rb b/app/controllers/concerns/project_unauthorized.rb
new file mode 100644
index 00000000000..f59440dbc59
--- /dev/null
+++ b/app/controllers/concerns/project_unauthorized.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ProjectUnauthorized
+ extend ActiveSupport::Concern
+
+ # EE would override this
+ def project_unauthorized_proc
+ # no-op
+ end
+end
diff --git a/app/controllers/concerns/routable_actions.rb b/app/controllers/concerns/routable_actions.rb
index 88939b002b2..5624eb3aa45 100644
--- a/app/controllers/concerns/routable_actions.rb
+++ b/app/controllers/concerns/routable_actions.rb
@@ -3,23 +3,25 @@
module RoutableActions
extend ActiveSupport::Concern
- def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil)
+ def find_routable!(routable_klass, requested_full_path, extra_authorization_proc: nil, not_found_or_authorized_proc: nil)
routable = routable_klass.find_by_full_path(requested_full_path, follow_redirects: request.get?)
if routable_authorized?(routable, extra_authorization_proc)
ensure_canonical_path(routable, requested_full_path)
routable
else
- handle_not_found_or_authorized(routable)
+ if not_found_or_authorized_proc
+ not_found_or_authorized_proc.call(routable)
+ end
+
+ route_not_found unless performed?
+
nil
end
end
- # This is overridden in gitlab-ee.
- def handle_not_found_or_authorized(_routable)
- route_not_found
- end
-
def routable_authorized?(routable, extra_authorization_proc)
+ return false unless routable
+
action = :"read_#{routable.class.to_s.underscore}"
return false unless can?(current_user, action, routable)
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index a2bdcaefa9b..e0677ce3fbc 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -3,6 +3,7 @@
class Projects::ApplicationController < ApplicationController
include CookiesHelper
include RoutableActions
+ include ProjectUnauthorized
include ChecksCollaboration
skip_before_action :authenticate_user!
@@ -21,7 +22,7 @@ class Projects::ApplicationController < ApplicationController
path = File.join(params[:namespace_id], params[:project_id] || params[:id])
auth_proc = ->(project) { !project.pending_delete? }
- @project = find_routable!(Project, path, extra_authorization_proc: auth_proc)
+ @project = find_routable!(Project, path, extra_authorization_proc: auth_proc, not_found_or_authorized_proc: project_unauthorized_proc)
end
def build_canonical_path(project)
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index bcea96bce94..c7b6218d007 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -1,29 +1,17 @@
# frozen_string_literal: true
-class Projects::Clusters::ApplicationsController < Projects::ApplicationController
- before_action :cluster
- before_action :authorize_read_cluster!
- before_action :authorize_create_cluster!, only: [:create]
+class Projects::Clusters::ApplicationsController < Clusters::ApplicationsController
+ include ProjectUnauthorized
- def create
- Clusters::Applications::CreateService
- .new(@cluster, current_user, create_cluster_application_params)
- .execute(request)
-
- head :no_content
- rescue Clusters::Applications::CreateService::InvalidApplicationError
- render_404
- rescue StandardError
- head :bad_request
- end
+ prepend_before_action :project
private
- def cluster
- @cluster ||= project.clusters.find(params[:id]) || render_404
+ def clusterable
+ @clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
- def create_cluster_application_params
- params.permit(:application, :hostname)
+ def project
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index 62adc66fb09..feda6deeaa6 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -1,224 +1,24 @@
# frozen_string_literal: true
-class Projects::ClustersController < Projects::ApplicationController
- before_action :cluster, except: [:index, :new, :create_gcp, :create_user]
- before_action :authorize_read_cluster!
- before_action :generate_gcp_authorize_url, only: [:new]
- before_action :validate_gcp_token, only: [:new]
- before_action :gcp_cluster, only: [:new]
- before_action :user_cluster, only: [:new]
- before_action :authorize_create_cluster!, only: [:new]
- before_action :authorize_update_cluster!, only: [:update]
- before_action :authorize_admin_cluster!, only: [:destroy]
- before_action :update_applications_status, only: [:status]
- helper_method :token_in_session
+class Projects::ClustersController < Clusters::ClustersController
+ include ProjectUnauthorized
- STATUS_POLLING_INTERVAL = 10_000
+ prepend_before_action :project
+ before_action :repository
- def index
- clusters = ClustersFinder.new(project, current_user, :all).execute
- @clusters = clusters.page(params[:page]).per(20)
- end
-
- def new
- end
-
- def status
- respond_to do |format|
- format.json do
- Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
-
- render json: ClusterSerializer
- .new(project: @project, current_user: @current_user)
- .represent_status(@cluster)
- end
- end
- end
-
- def show
- end
-
- def update
- Clusters::UpdateService
- .new(current_user, update_params)
- .execute(cluster)
-
- if cluster.valid?
- respond_to do |format|
- format.json do
- head :no_content
- end
- format.html do
- flash[:notice] = _('Kubernetes cluster was successfully updated.')
- redirect_to project_cluster_path(project, cluster)
- end
- end
- else
- respond_to do |format|
- format.json { head :bad_request }
- format.html { render :show }
- end
- end
- end
-
- def destroy
- if cluster.destroy
- flash[:notice] = _('Kubernetes cluster integration was successfully removed.')
- redirect_to project_clusters_path(project), status: :found
- else
- flash[:notice] = _('Kubernetes cluster integration was not removed.')
- render :show
- end
- end
-
- def create_gcp
- @gcp_cluster = ::Clusters::CreateService
- .new(current_user, create_gcp_cluster_params)
- .execute(project: project, access_token: token_in_session)
-
- if @gcp_cluster.persisted?
- redirect_to project_cluster_path(project, @gcp_cluster)
- else
- generate_gcp_authorize_url
- validate_gcp_token
- user_cluster
-
- render :new, locals: { active_tab: 'gcp' }
- end
- end
-
- def create_user
- @user_cluster = ::Clusters::CreateService
- .new(current_user, create_user_cluster_params)
- .execute(project: project, access_token: token_in_session)
-
- if @user_cluster.persisted?
- redirect_to project_cluster_path(project, @user_cluster)
- else
- generate_gcp_authorize_url
- validate_gcp_token
- gcp_cluster
-
- render :new, locals: { active_tab: 'user' }
- end
- end
+ layout 'project'
private
- def cluster
- @cluster ||= project.clusters.find(params[:id])
- .present(current_user: current_user)
- end
-
- def update_params
- if cluster.managed?
- params.require(:cluster).permit(
- :enabled,
- :environment_scope,
- platform_kubernetes_attributes: [
- :namespace
- ]
- )
- else
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- platform_kubernetes_attributes: [
- :api_url,
- :token,
- :ca_cert,
- :namespace
- ]
- )
- end
- end
-
- def create_gcp_cluster_params
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- provider_gcp_attributes: [
- :gcp_project_id,
- :zone,
- :num_nodes,
- :machine_type,
- :legacy_abac
- ]).merge(
- provider_type: :gcp,
- platform_type: :kubernetes
- )
- end
-
- def create_user_cluster_params
- params.require(:cluster).permit(
- :enabled,
- :name,
- :environment_scope,
- platform_kubernetes_attributes: [
- :namespace,
- :api_url,
- :token,
- :ca_cert,
- :authorization_type
- ]).merge(
- provider_type: :user,
- platform_type: :kubernetes
- )
- end
-
- def generate_gcp_authorize_url
- state = generate_session_key_redirect(new_project_cluster_path(@project).to_s)
-
- @authorize_url = GoogleApi::CloudPlatform::Client.new(
- nil, callback_google_api_auth_url,
- state: state).authorize_url
- rescue GoogleApi::Auth::ConfigMissingError
- # no-op
- end
-
- def gcp_cluster
- @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_provider_gcp
- end
- end
-
- def user_cluster
- @user_cluster = ::Clusters::Cluster.new.tap do |cluster|
- cluster.build_platform_kubernetes
- end
- end
-
- def validate_gcp_token
- @valid_gcp_token = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
- .validate_token(expires_at_in_session)
- end
-
- def token_in_session
- session[GoogleApi::CloudPlatform::Client.session_key_for_token]
- end
-
- def expires_at_in_session
- @expires_at_in_session ||=
- session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
- end
-
- def generate_session_key_redirect(uri)
- GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
- session[key] = uri
- end
- end
-
- def authorize_update_cluster!
- access_denied! unless can?(current_user, :update_cluster, cluster)
+ def clusterable
+ @clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
- def authorize_admin_cluster!
- access_denied! unless can?(current_user, :admin_cluster, cluster)
+ def project
+ @project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]), not_found_or_authorized_proc: project_unauthorized_proc)
end
- def update_applications_status
- @cluster.applications.each(&:schedule_status_update)
+ def repository
+ @repository ||= project.repository
end
end
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 00b63f55710..32fc5140366 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -43,7 +43,7 @@ class Projects::CommitController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
- @pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
+ @pipelines = @pipelines.where(ref: params[:ref]).page(params[:page]).per(30) if params[:ref]
respond_to do |format|
format.html
@@ -53,6 +53,7 @@ class Projects::CommitController < Projects::ApplicationController
render json: {
pipelines: PipelineSerializer
.new(project: @project, current_user: @current_user)
+ .with_pagination(request, response)
.represent(@pipelines),
count: {
all: @pipelines.count
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 27b83da4f54..4bdb857b2d9 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -84,13 +84,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def pipelines
- @pipelines = @merge_request.all_pipelines
+ @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30)
Gitlab::PollingInterval.set_header(response, interval: 10_000)
render json: {
pipelines: PipelineSerializer
.new(project: @project, current_user: @current_user)
+ .with_pagination(request, response)
.represent(@pipelines),
count: {
all: @pipelines.count
diff --git a/app/finders/clusters_finder.rb b/app/finders/clusters_finder.rb
index b40d6c41b71..0cce493b73e 100644
--- a/app/finders/clusters_finder.rb
+++ b/app/finders/clusters_finder.rb
@@ -1,20 +1,20 @@
# frozen_string_literal: true
class ClustersFinder
- def initialize(project, user, scope)
- @project = project
+ def initialize(clusterable, user, scope)
+ @clusterable = clusterable
@user = user
@scope = scope || :active
end
def execute
- clusters = project.clusters
+ clusters = clusterable.clusters
filter_by_scope(clusters)
end
private
- attr_reader :project, :user, :scope
+ attr_reader :clusterable, :user, :scope
def filter_by_scope(clusters)
case scope.to_sym
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 19eb763e1de..916dcb1a308 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module ClustersHelper
- def has_multiple_clusters?(project)
+ # EE overrides this
+ def has_multiple_clusters?
false
end
@@ -10,7 +11,7 @@ module ClustersHelper
return unless show_gcp_signup_offer?
content_tag :section, class: 'no-animate expanded' do
- render 'projects/clusters/gcp_signup_offer_banner'
+ render 'clusters/clusters/gcp_signup_offer_banner'
end
end
end
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 76ed8efe2c6..39f661b5f0c 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -143,7 +143,7 @@ module LabelsHelper
def labels_filter_path(options = {})
project = @target_project || @project
- format = options.delete(:format) || :html
+ format = options.delete(:format)
if project
project_labels_path(project, format, options)
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 602e5afe26b..93b51fb1774 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -45,6 +45,20 @@ module Emails
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
end
+ def removed_milestone_issue_email(recipient_id, issue_id, updated_by_user_id, reason = nil)
+ setup_issue_mail(issue_id, recipient_id)
+
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
+ end
+
+ def changed_milestone_issue_email(recipient_id, issue_id, milestone, updated_by_user_id, reason = nil)
+ setup_issue_mail(issue_id, recipient_id)
+
+ @milestone = milestone
+ @milestone_url = milestone_url(@milestone)
+ mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id, reason))
+ end
+
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id, reason = nil)
setup_issue_mail(issue_id, recipient_id)
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index be085496731..6524d0c2087 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -40,6 +40,20 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
end
+ def removed_milestone_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
+ setup_merge_request_mail(merge_request_id, recipient_id)
+
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
+ end
+
+ def changed_milestone_merge_request_email(recipient_id, merge_request_id, milestone, updated_by_user_id, reason = nil)
+ setup_merge_request_mail(merge_request_id, recipient_id)
+
+ @milestone = milestone
+ @milestone_url = milestone_url(@milestone)
+ mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason))
+ end
+
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 2f5b5483e9d..e7e8d96eca4 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -68,6 +68,14 @@ class NotifyPreview < ActionMailer::Preview
Notify.issue_status_changed_email(user.id, issue.id, 'closed', user.id).message
end
+ def removed_milestone_issue_email
+ Notify.removed_milestone_issue_email(user.id, issue.id, user.id)
+ end
+
+ def changed_milestone_issue_email
+ Notify.changed_milestone_issue_email(user.id, issue.id, milestone, user.id)
+ end
+
def closed_merge_request_email
Notify.closed_merge_request_email(user.id, issue.id, user.id).message
end
@@ -80,6 +88,14 @@ class NotifyPreview < ActionMailer::Preview
Notify.merged_merge_request_email(user.id, merge_request.id, user.id).message
end
+ def removed_milestone_merge_request_email
+ Notify.removed_milestone_merge_request_email(user.id, merge_request.id, user.id)
+ end
+
+ def changed_milestone_merge_request_email
+ Notify.changed_milestone_merge_request_email(user.id, merge_request.id, milestone, user.id)
+ end
+
def member_access_denied_email
Notify.member_access_denied_email('project', project.id, user.id).message
end
@@ -143,6 +159,10 @@ class NotifyPreview < ActionMailer::Preview
@merge_request ||= project.merge_requests.first
end
+ def milestone
+ @milestone ||= issue.milestone
+ end
+
def pipeline
@pipeline = Ci::Pipeline.last
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index d73c02ba5d7..bb5d52fc78d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -259,8 +259,7 @@ module Ci
end
def schedulable?
- Feature.enabled?('ci_enable_scheduled_build', default_enabled: true) &&
- self.when == 'delayed' && options[:start_in].present?
+ self.when == 'delayed' && options[:start_in].present?
end
def options_scheduled_at
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 1939df9f86e..7b219ec8267 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -20,6 +20,7 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
+ has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
@@ -131,6 +132,13 @@ 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
+ )
+ end
+
private
def restrict_modification
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index fb5f6b65d9d..ac7f9193b87 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -2,6 +2,8 @@
module Clusters
class KubernetesNamespace < ActiveRecord::Base
+ include Gitlab::Kubernetes
+
self.table_name = 'clusters_kubernetes_namespaces'
belongs_to :cluster_project, class_name: 'Clusters::Project'
@@ -12,7 +14,8 @@ module Clusters
validates :namespace, presence: true
validates :namespace, uniqueness: { scope: :cluster_id }
- before_validation :set_namespace_and_service_account_to_default, on: :create
+ delegate :ca_pem, to: :platform_kubernetes, allow_nil: true
+ delegate :api_url, to: :platform_kubernetes, allow_nil: true
attr_encrypted :service_account_token,
mode: :per_attribute_iv,
@@ -23,14 +26,26 @@ module Clusters
"#{namespace}-token"
end
- private
+ def configure_predefined_credentials
+ self.namespace = kubernetes_or_project_namespace
+ self.service_account_name = default_service_account_name
+ end
+
+ def predefined_variables
+ config = YAML.dump(kubeconfig)
- def set_namespace_and_service_account_to_default
- self.namespace ||= default_namespace
- self.service_account_name ||= default_service_account_name
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ variables
+ .append(key: 'KUBE_SERVICE_ACCOUNT', value: service_account_name)
+ .append(key: 'KUBE_NAMESPACE', value: namespace)
+ .append(key: 'KUBE_TOKEN', value: service_account_token, public: false)
+ .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+ end
end
- def default_namespace
+ private
+
+ def kubernetes_or_project_namespace
platform_kubernetes&.namespace.presence || project_namespace
end
@@ -45,5 +60,13 @@ module Clusters
def project_slug
"#{project.path}-#{project.id}".downcase
end
+
+ def kubeconfig
+ to_kubeconfig(
+ url: api_url,
+ namespace: namespace,
+ token: service_account_token,
+ ca_pem: ca_pem)
+ end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index d961130d251..d69038be532 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -6,6 +6,7 @@ module Clusters
include Gitlab::Kubernetes
include ReactiveCaching
include EnumWithNil
+ include AfterCommitQueue
RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
@@ -44,6 +45,7 @@ module Clusters
validate :prevent_modification, on: :update
after_save :clear_reactive_cache!
+ after_update :update_kubernetes_namespace
alias_attribute :ca_pem, :ca_cert
@@ -68,21 +70,31 @@ module Clusters
end
end
- def predefined_variables
- config = YAML.dump(kubeconfig)
-
+ def predefined_variables(project:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
- variables
- .append(key: 'KUBE_URL', value: api_url)
- .append(key: 'KUBE_TOKEN', value: token, public: false)
- .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
- .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+ variables.append(key: 'KUBE_URL', value: api_url)
if ca_pem.present?
variables
.append(key: 'KUBE_CA_PEM', value: ca_pem)
.append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true)
end
+
+ if kubernetes_namespace = cluster.kubernetes_namespaces.find_by(project: project)
+ variables.concat(kubernetes_namespace.predefined_variables)
+ else
+ # From 11.5, every Clusters::Project should have at least one
+ # Clusters::KubernetesNamespace, so once migration has been completed,
+ # this 'else' branch will be removed. For more information, please see
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22433
+ config = YAML.dump(kubeconfig)
+
+ variables
+ .append(key: 'KUBE_URL', value: api_url)
+ .append(key: 'KUBE_TOKEN', value: token, public: false)
+ .append(key: 'KUBE_NAMESPACE', value: actual_namespace)
+ .append(key: 'KUBECONFIG', value: config, public: false, file: true)
+ end
end
end
@@ -205,6 +217,14 @@ module Clusters
true
end
+
+ def update_kubernetes_namespace
+ return unless namespace_changed?
+
+ run_after_commit do
+ ClusterPlatformConfigureWorker.perform_async(cluster_id)
+ end
+ end
end
end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 66db4bd92de..23a43aec677 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -10,6 +10,7 @@ module TokenAuthenticatable
def add_authentication_token_field(token_field, options = {})
@token_fields = [] unless @token_fields
+ unique = options.fetch(:unique, true)
if @token_fields.include?(token_field)
raise ArgumentError.new("#{token_field} already configured via add_authentication_token_field")
@@ -25,8 +26,10 @@ module TokenAuthenticatable
TokenAuthenticatableStrategies::Insecure.new(self, token_field, options)
end
- define_singleton_method("find_by_#{token_field}") do |token|
- strategy.find_token_authenticatable(token)
+ if unique
+ define_singleton_method("find_by_#{token_field}") do |token|
+ strategy.find_token_authenticatable(token)
+ end
end
define_method(token_field) do
diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb
index f0f7107d627..413721d3e6c 100644
--- a/app/models/concerns/token_authenticatable_strategies/base.rb
+++ b/app/models/concerns/token_authenticatable_strategies/base.rb
@@ -43,10 +43,14 @@ module TokenAuthenticatableStrategies
set_token(instance, new_token)
end
+ def unique
+ @options.fetch(:unique, true)
+ end
+
def generate_available_token
loop do
token = generate_token
- break token unless find_token_authenticatable(token, true)
+ break token unless unique && find_token_authenticatable(token, true)
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 4ace5d3ab97..0de5e434b02 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -240,7 +240,8 @@ class Issue < ActiveRecord::Base
reference_path: issue_reference,
real_path: url_helper.project_issue_path(project, self),
issue_sidebar_endpoint: url_helper.project_issue_path(project, self, format: :json, serializer: 'sidebar'),
- toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self)
+ toggle_subscription_endpoint: url_helper.toggle_subscription_project_issue_path(project, self),
+ assignable_labels_endpoint: url_helper.project_labels_path(project, format: :json, include_ancestor_groups: true)
)
end
diff --git a/app/models/key.rb b/app/models/key.rb
index bdb83e12793..8f93418b88b 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -34,6 +34,10 @@ class Key < ActiveRecord::Base
after_destroy :post_destroy_hook
after_destroy :refresh_user_cache
+ def self.regular_keys
+ where(type: ['Key', nil])
+ end
+
def key=(value)
write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7eef08aa6a3..735d9fba966 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -353,6 +353,15 @@ class MergeRequest < ActiveRecord::Base
end
end
+ # Returns true if there are commits that match at least one commit SHA.
+ def includes_any_commits?(shas)
+ if persisted?
+ merge_request_diff.commits_by_shas(shas).exists?
+ else
+ (commit_shas & shas).present?
+ end
+ end
+
# Calls `MergeWorker` to proceed with the merge process and
# updates `merge_jid` with the MergeWorker#jid.
# This helps tracking enqueued and ongoing merge jobs.
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 02c6b650f33..bb6ff8921df 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -140,6 +140,12 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request_diff_commits.map(&:sha)
end
+ def commits_by_shas(shas)
+ return [] unless shas.present?
+
+ merge_request_diff_commits.where(sha: shas)
+ end
+
def diff_refs=(new_diff_refs)
self.base_commit_sha = new_diff_refs&.base_sha
self.start_commit_sha = new_diff_refs&.start_sha
diff --git a/app/models/project.rb b/app/models/project.rb
index e2e309e8496..fa995b5b061 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1829,7 +1829,7 @@ class Project < ActiveRecord::Base
end
def deployment_variables(environment: nil)
- deployment_platform(environment: environment)&.predefined_variables || []
+ deployment_platform(environment: environment)&.predefined_variables(project: self) || []
end
def auto_devops_variables
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 798944d0c06..3459ded7ccf 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -104,7 +104,12 @@ class KubernetesService < DeploymentService
{ success: false, result: err }
end
- def predefined_variables
+ # Project param was added on
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011,
+ # as a way to keep this service compatible with
+ # Clusters::Platforms::Kubernetes, it won't be used on this method
+ # as it's only needed for Clusters::Cluster.
+ def predefined_variables(project:)
config = YAML.dump(kubeconfig)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
diff --git a/app/models/user.rb b/app/models/user.rb
index cc2cd1b7723..d3eb7162174 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -88,7 +88,7 @@ class User < ActiveRecord::Base
has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
# Profile
- has_many :keys, -> { where(type: ['Key', nil]) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
has_many :gpg_keys
@@ -941,12 +941,17 @@ class User < ActiveRecord::Base
if !Gitlab.config.ldap.enabled
false
elsif ldap_user?
- !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
+ !last_credential_check_at || (last_credential_check_at + ldap_sync_time) < Time.now
else
false
end
end
+ def ldap_sync_time
+ # This number resides in this method so it can be redefined in EE.
+ 1.hour
+ end
+
def try_obtain_ldap_lease
# After obtaining this lease LDAP checks will be blocked for 600 seconds
# (10 minutes) for this user.
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
new file mode 100644
index 00000000000..cff0e74d6ea
--- /dev/null
+++ b/app/presenters/clusterable_presenter.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class ClusterablePresenter < Gitlab::View::Presenter::Delegated
+ presents :clusterable
+
+ def self.fabricate(clusterable, **attributes)
+ presenter_class = "#{clusterable.class.name}ClusterablePresenter".constantize
+ attributes_with_presenter_class = attributes.merge(presenter_class: presenter_class)
+
+ Gitlab::View::Presenter::Factory
+ .new(clusterable, attributes_with_presenter_class)
+ .fabricate!
+ end
+
+ def can_create_cluster?
+ can?(current_user, :create_cluster, clusterable)
+ end
+
+ def index_path
+ polymorphic_path([clusterable, :clusters])
+ end
+
+ def new_path
+ new_polymorphic_path([clusterable, :cluster])
+ end
+
+ def create_user_clusters_path
+ polymorphic_path([clusterable, :clusters], action: :create_user)
+ end
+
+ def create_gcp_clusters_path
+ polymorphic_path([clusterable, :clusters], action: :create_gcp)
+ end
+
+ def cluster_status_cluster_path(cluster, params = {})
+ raise NotImplementedError
+ end
+
+ def install_applications_cluster_path(cluster, application)
+ raise NotImplementedError
+ end
+
+ def cluster_path(cluster, params = {})
+ raise NotImplementedError
+ end
+end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index dfdd8e82f97..78d632eb77c 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -11,5 +11,13 @@ module Clusters
def can_toggle_cluster?
can?(current_user, :update_cluster, cluster) && created?
end
+
+ def show_path
+ if cluster.project_type?
+ project_cluster_path(project, cluster)
+ else
+ raise NotImplementedError
+ end
+ end
end
end
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
new file mode 100644
index 00000000000..12077b2e735
--- /dev/null
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ProjectClusterablePresenter < ClusterablePresenter
+ def cluster_status_cluster_path(cluster, params = {})
+ cluster_status_project_cluster_path(clusterable, cluster, params)
+ end
+
+ def install_applications_cluster_path(cluster, application)
+ install_applications_project_cluster_path(clusterable, cluster, application)
+ end
+
+ def cluster_path(cluster, params = {})
+ project_cluster_path(clusterable, cluster, params)
+ end
+end
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
index 0db7875aa87..95833c3528f 100644
--- a/app/serializers/build_action_entity.rb
+++ b/app/serializers/build_action_entity.rb
@@ -12,7 +12,8 @@ class BuildActionEntity < Grape::Entity
end
expose :playable?, as: :playable
- expose :scheduled_at, if: -> (build) { build.scheduled? }
+ expose :scheduled?, as: :scheduled
+ expose :scheduled_at, if: -> (*) { scheduled? }
expose :unschedule_path, if: -> (build) { build.scheduled? } do |build|
unschedule_project_job_path(build.project, build)
@@ -25,4 +26,8 @@ class BuildActionEntity < Grape::Entity
def playable?
build.playable? && can?(request.current_user, :update_build, build)
end
+
+ def scheduled?
+ build.scheduled?
+ end
end
diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb
index a0a66511b7b..aebbc18e32f 100644
--- a/app/serializers/job_entity.rb
+++ b/app/serializers/job_entity.rb
@@ -33,6 +33,7 @@ class JobEntity < Grape::Entity
end
expose :playable?, as: :playable
+ expose :scheduled?, as: :scheduled
expose :scheduled_at, if: -> (*) { scheduled? }
expose :created_at
expose :updated_at
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index cd843b8ffa8..270db4a52fd 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -8,10 +8,11 @@ module Clusters
@current_user, @params = user, params.dup
end
- def execute(project:, access_token: nil)
- raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
+ def execute(access_token: nil)
+ raise ArgumentError, 'Unknown clusterable provided' unless clusterable
+ raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?
- cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
+ cluster_params = params.merge(user: current_user).merge(clusterable_params)
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
@@ -27,9 +28,20 @@ module Clusters
Clusters::Cluster.create(cluster_params)
end
+ def clusterable
+ @clusterable ||= params.delete(:clusterable)
+ end
+
+ def clusterable_params
+ case clusterable
+ when ::Project
+ { cluster_type: :project_type, projects: [clusterable] }
+ end
+ end
+
# EE would override this method
- def can_create_cluster?(project)
- project.clusters.empty?
+ def can_create_cluster?
+ clusterable.clusters.empty?
end
end
end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 6ee63db8eb9..3df43657fa0 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -11,8 +11,9 @@ module Clusters
configure_provider
create_gitlab_service_account!
configure_kubernetes
-
cluster.save!
+ configure_project_service_account
+
rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e
provider.make_errored!("Failed to request to CloudPlatform; #{e.message}")
rescue Kubeclient::HttpError => e
@@ -24,7 +25,10 @@ module Clusters
private
def create_gitlab_service_account!
- Clusters::Gcp::Kubernetes::CreateServiceAccountService.new(kube_client, rbac: create_rbac_cluster?).execute
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.gitlab_creator(
+ kube_client,
+ rbac: create_rbac_cluster?
+ ).execute
end
def configure_provider
@@ -44,7 +48,20 @@ module Clusters
end
def request_kubernetes_token
- Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(kube_client).execute
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
+ kube_client,
+ Clusters::Gcp::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
+ Clusters::Gcp::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE
+ ).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
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
index d014d73b3e8..90ed529670c 100644
--- a/app/services/clusters/gcp/kubernetes.rb
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -3,11 +3,12 @@
module Clusters
module Gcp
module Kubernetes
- SERVICE_ACCOUNT_NAME = 'gitlab'
- SERVICE_ACCOUNT_NAMESPACE = 'default'
- SERVICE_ACCOUNT_TOKEN_NAME = 'gitlab-token'
- CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
- CLUSTER_ROLE_NAME = 'cluster-admin'
+ GITLAB_SERVICE_ACCOUNT_NAME = 'gitlab'
+ GITLAB_SERVICE_ACCOUNT_NAMESPACE = 'default'
+ GITLAB_ADMIN_TOKEN_NAME = 'gitlab-token'
+ GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
+ GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
+ PROJECT_CLUSTER_ROLE_NAME = 'edit'
end
end
end
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
new file mode 100644
index 00000000000..a888fab2789
--- /dev/null
+++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Gcp
+ module Kubernetes
+ class CreateOrUpdateNamespaceService
+ def initialize(cluster:, kubernetes_namespace:)
+ @cluster = cluster
+ @kubernetes_namespace = kubernetes_namespace
+ @platform = cluster.platform
+ end
+
+ def execute
+ configure_kubernetes_namespace
+ create_project_service_account
+ configure_kubernetes_token
+
+ kubernetes_namespace.save!
+ rescue ::Kubeclient::HttpError => err
+ raise err unless err.error_code = 404
+ end
+
+ private
+
+ attr_reader :cluster, :kubernetes_namespace, :platform
+
+ def configure_kubernetes_namespace
+ kubernetes_namespace.configure_predefined_credentials
+ end
+
+ def create_project_service_account
+ Clusters::Gcp::Kubernetes::CreateServiceAccountService.namespace_creator(
+ platform.kubeclient,
+ service_account_name: kubernetes_namespace.service_account_name,
+ service_account_namespace: kubernetes_namespace.namespace,
+ rbac: platform.rbac?
+ ).execute
+ end
+
+ def configure_kubernetes_token
+ kubernetes_namespace.service_account_token = fetch_service_account_token
+ end
+
+ def fetch_service_account_token
+ Clusters::Gcp::Kubernetes::FetchKubernetesTokenService.new(
+ platform.kubeclient,
+ kubernetes_namespace.token_name,
+ kubernetes_namespace.namespace
+ ).execute
+ 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
index d17744591e6..dfc4bf7a358 100644
--- a/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
+++ b/app/services/clusters/gcp/kubernetes/create_service_account_service.rb
@@ -4,46 +4,96 @@ module Clusters
module Gcp
module Kubernetes
class CreateServiceAccountService
- attr_reader :kubeclient, :rbac
-
- def initialize(kubeclient, rbac:)
+ 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_service_account(service_account_resource)
kubeclient.create_secret(service_account_token_resource)
- kubeclient.create_cluster_role_binding(cluster_role_binding_resource) if rbac
+ 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_role_binding(role_binding_resource)
+ else
+ kubeclient.create_cluster_role_binding(cluster_role_binding_resource)
+ end
+ end
+
def service_account_resource
- Gitlab::Kubernetes::ServiceAccount.new(service_account_name, service_account_namespace).generate
+ Gitlab::Kubernetes::ServiceAccount.new(
+ service_account_name,
+ service_account_namespace
+ ).generate
end
def service_account_token_resource
Gitlab::Kubernetes::ServiceAccountToken.new(
- SERVICE_ACCOUNT_TOKEN_NAME, service_account_name, service_account_namespace).generate
+ 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(
- CLUSTER_ROLE_BINDING_NAME,
- CLUSTER_ROLE_NAME,
+ Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_BINDING_NAME,
+ Clusters::Gcp::Kubernetes::GITLAB_CLUSTER_ROLE_NAME,
subjects
).generate
end
- def service_account_name
- SERVICE_ACCOUNT_NAME
- end
-
- def service_account_namespace
- SERVICE_ACCOUNT_NAMESPACE
+ 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
diff --git a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
index 9e09345c8dc..277cc4b788d 100644
--- a/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
+++ b/app/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service.rb
@@ -4,10 +4,12 @@ module Clusters
module Gcp
module Kubernetes
class FetchKubernetesTokenService
- attr_reader :kubeclient
+ attr_reader :kubeclient, :service_account_token_name, :namespace
- def initialize(kubeclient)
+ def initialize(kubeclient, service_account_token_name, namespace)
@kubeclient = kubeclient
+ @service_account_token_name = service_account_token_name
+ @namespace = namespace
end
def execute
@@ -18,7 +20,7 @@ module Clusters
private
def get_secret
- kubeclient.get_secret(SERVICE_ACCOUNT_TOKEN_NAME, SERVICE_ACCOUNT_NAMESPACE).as_json
+ kubeclient.get_secret(service_account_token_name, namespace).as_json
rescue Kubeclient::HttpError => err
raise err unless err.error_code == 404
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 3e8b9f84042..c388913ae65 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -3,6 +3,14 @@
class IssuableBaseService < BaseService
private
+ attr_accessor :params, :skip_milestone_email
+
+ def initialize(project, user = nil, params = {})
+ super
+
+ @skip_milestone_email = @params.delete(:skip_milestone_email)
+ end
+
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index b54b0bf6ef6..fba252b0bae 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -48,6 +48,8 @@ module Issues
notification_service.async.relabeled_issue(issue, added_labels, current_user)
end
+ handle_milestone_change(issue)
+
added_mentions = issue.mentioned_users - old_mentioned_users
if added_mentions.present?
@@ -91,6 +93,18 @@ module Issues
private
+ def handle_milestone_change(issue)
+ return if skip_milestone_email
+
+ return unless issue.previous_changes.include?('milestone_id')
+
+ if issue.milestone.nil?
+ notification_service.async.removed_milestone_issue(issue, current_user)
+ else
+ notification_service.async.changed_milestone_issue(issue, issue.milestone, current_user)
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def get_issue_if_allowed(id, board_group_id = nil)
return unless id
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index f01872b205e..53768ff2cbe 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -87,11 +87,8 @@ module MergeRequests
filter_merge_requests(merge_requests).each do |merge_request|
if branch_and_project_match?(merge_request) || @push.force_push?
merge_request.reload_diff(current_user)
- else
- mr_commit_ids = merge_request.commit_shas
- push_commit_ids = @commits.map(&:id)
- matches = mr_commit_ids & push_commit_ids
- merge_request.reload_diff(current_user) if matches.any?
+ elsif merge_request.includes_any_commits?(push_commit_ids)
+ merge_request.reload_diff(current_user)
end
merge_request.mark_as_unchecked
@@ -104,6 +101,10 @@ module MergeRequests
end
# rubocop: enable CodeReuse/ActiveRecord
+ def push_commit_ids
+ @push_commit_ids ||= @commits.map(&:id)
+ end
+
def branch_and_project_match?(merge_request)
merge_request.source_project == @project &&
merge_request.source_branch == @push.branch_name
diff --git a/app/services/merge_requests/reload_diffs_service.rb b/app/services/merge_requests/reload_diffs_service.rb
index b4d48fe92ad..b47d8f3f63a 100644
--- a/app/services/merge_requests/reload_diffs_service.rb
+++ b/app/services/merge_requests/reload_diffs_service.rb
@@ -36,7 +36,10 @@ module MergeRequests
# Remove cache for all diffs on this MR. Do not use the association on the
# model, as that will interfere with other actions happening when
# reloading the diff.
- MergeRequestDiff.where(merge_request: merge_request).each do |merge_request_diff|
+ MergeRequestDiff
+ .where(merge_request: merge_request)
+ .preload(merge_request: :target_project)
+ .find_each do |merge_request_diff|
next if merge_request_diff == new_diff
cacheable_collection(merge_request_diff).clear_cache
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index b112edbce7f..aacaf10d09c 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -58,6 +58,8 @@ module MergeRequests
merge_request.mark_as_unchecked
end
+ handle_milestone_change(merge_request)
+
added_labels = merge_request.labels - old_labels
if added_labels.present?
notification_service.async.relabeled_merge_request(
@@ -105,6 +107,18 @@ module MergeRequests
private
+ def handle_milestone_change(merge_request)
+ return if skip_milestone_email
+
+ return unless merge_request.previous_changes.include?('milestone_id')
+
+ if merge_request.milestone.nil?
+ notification_service.async.removed_milestone_merge_request(merge_request, current_user)
+ else
+ notification_service.async.changed_milestone_merge_request(merge_request, merge_request.milestone, current_user)
+ end
+ end
+
def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
SystemNoteService.change_branch(
issuable, issuable.project, current_user, branch_type,
diff --git a/app/services/milestones/destroy_service.rb b/app/services/milestones/destroy_service.rb
index 7cda802c120..87c7a282081 100644
--- a/app/services/milestones/destroy_service.rb
+++ b/app/services/milestones/destroy_service.rb
@@ -4,7 +4,7 @@ module Milestones
class DestroyService < Milestones::BaseService
def execute(milestone)
Milestone.transaction do
- update_params = { milestone: nil }
+ update_params = { milestone: nil, skip_milestone_email: true }
milestone.issues.each do |issue|
Issues::UpdateService.new(parent, current_user, update_params).execute(issue)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 50fa373025b..fb9c18ea75d 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -129,6 +129,14 @@ class NotificationService
relabeled_resource_email(issue, added_labels, current_user, :relabeled_issue_email)
end
+ def removed_milestone_issue(issue, current_user)
+ removed_milestone_resource_email(issue, current_user, :removed_milestone_issue_email)
+ end
+
+ def changed_milestone_issue(issue, new_milestone, current_user)
+ changed_milestone_resource_email(issue, new_milestone, current_user, :changed_milestone_issue_email)
+ end
+
# When create a merge request we should send an email to:
#
# * mr author
@@ -138,7 +146,6 @@ class NotificationService
# * users with custom level checked with "new merge request"
#
# In EE, approvers of the merge request are also included
- #
def new_merge_request(merge_request, current_user)
new_resource_email(merge_request, :new_merge_request_email)
end
@@ -208,6 +215,14 @@ class NotificationService
relabeled_resource_email(merge_request, added_labels, current_user, :relabeled_merge_request_email)
end
+ def removed_milestone_merge_request(merge_request, current_user)
+ removed_milestone_resource_email(merge_request, current_user, :removed_milestone_merge_request_email)
+ end
+
+ def changed_milestone_merge_request(merge_request, new_milestone, current_user)
+ changed_milestone_resource_email(merge_request, new_milestone, current_user, :changed_milestone_merge_request_email)
+ end
+
def close_mr(merge_request, current_user)
close_resource_email(merge_request, current_user, :closed_merge_request_email)
end
@@ -500,6 +515,30 @@ class NotificationService
end
end
+ def removed_milestone_resource_email(target, current_user, method)
+ recipients = NotificationRecipientService.build_recipients(
+ target,
+ current_user,
+ action: 'removed_milestone'
+ )
+
+ recipients.each do |recipient|
+ mailer.send(method, recipient.user.id, target.id, current_user.id).deliver_later
+ end
+ end
+
+ def changed_milestone_resource_email(target, milestone, current_user, method)
+ recipients = NotificationRecipientService.build_recipients(
+ target,
+ current_user,
+ action: 'changed_milestone'
+ )
+
+ recipients.each do |recipient|
+ mailer.send(method, recipient.user.id, target.id, milestone, current_user.id).deliver_later
+ end
+ end
+
def reopen_resource_email(target, current_user, method, status)
recipients = NotificationRecipientService.build_recipients(target, current_user, action: "reopen")
diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 243e8cd9ba0..7037c80aa6b 100644
--- a/app/views/projects/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -12,4 +12,4 @@
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p
= s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
- = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
+ = link_to(s_('ClusterIntegration|Remove integration'), clusterable.cluster_path(@cluster), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index 73cfea0ef92..73cfea0ef92 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml
index 2d7f7c6b1fb..facbcb7fc59 100644
--- a/app/views/projects/clusters/_cluster.html.haml
+++ b/app/views/clusters/clusters/_cluster.html.haml
@@ -2,7 +2,7 @@
.table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster")
.table-mobile-content
- = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
+ = link_to cluster.name, cluster.show_path
.table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
.table-mobile-content= cluster.environment_scope
@@ -16,7 +16,7 @@
class: "#{'is-checked' if cluster.enabled?} #{'is-disabled' if !cluster.can_toggle_cluster?}",
"aria-label": s_("ClusterIntegration|Toggle Kubernetes Cluster"),
disabled: !cluster.can_toggle_cluster?,
- data: { endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
+ data: { endpoint: clusterable.cluster_path(cluster, format: :json) } }
%input.js-project-feature-toggle-input{ type: "hidden", value: cluster.enabled? }
= icon("spinner spin", class: "loading-icon")
%span.toggle-icon
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml
index b8a3556a206..800e76d92ef 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/clusters/clusters/_empty_state.html.haml
@@ -7,6 +7,6 @@
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
- - if can?(current_user, :create_cluster, @project)
+ - if clusterable.can_create_cluster?
.text-center
- = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
+ = link_to s_('ClusterIntegration|Add Kubernetes cluster'), clusterable.new_path, class: 'btn btn-success'
diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
index 73b11d509d3..73b11d509d3 100644
--- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml
diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/clusters/clusters/_integration_form.html.haml
index d0a553e3414..5e451f60c9d 100644
--- a/app/views/projects/clusters/_integration_form.html.haml
+++ b/app/views/clusters/clusters/_integration_form.html.haml
@@ -1,4 +1,4 @@
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
%h5= s_('ClusterIntegration|Integration status')
@@ -13,7 +13,7 @@
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- - if has_multiple_clusters?(@project)
+ - if has_multiple_clusters?
.form-group
%h5= s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
@@ -23,7 +23,7 @@
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
- - unless has_multiple_clusters?(@project)
+ - unless has_multiple_clusters?
%h5= s_('ClusterIntegration|Environment scope')
%p
%code *
diff --git a/app/views/projects/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 3d10348212f..3d10348212f 100644
--- a/app/views/projects/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 171ceeceb68..ad842036a62 100644
--- a/app/views/projects/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -12,14 +12,14 @@
%p= link_to('Select a different Google account', @authorize_url)
-= form_for @gcp_cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: create_gcp_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
+= form_for @gcp_cluster, html: { class: 'js-gke-cluster-creation prepend-top-20', data: { token: token_in_session } }, url: clusterable.create_gcp_clusters_path, as: :cluster do |field|
= form_errors(@gcp_cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
- = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
+ = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?, placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :provider_gcp, @gcp_cluster.provider_gcp do |provider_gcp_field|
.form-group
diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/clusters/clusters/gcp/_header.html.haml
index a2ad3cd64df..a2ad3cd64df 100644
--- a/app/views/projects/clusters/gcp/_header.html.haml
+++ b/app/views/clusters/clusters/gcp/_header.html.haml
diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/clusters/clusters/gcp/_show.html.haml
index 779c9c245c1..6021b220285 100644
--- a/app/views/projects/clusters/gcp/_show.html.haml
+++ b/app/views/clusters/clusters/gcp/_show.html.haml
@@ -6,7 +6,7 @@
%span.input-group-append
= clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'input-group-text btn-default')
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
diff --git a/app/views/projects/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml
index a55de84b5cd..a55de84b5cd 100644
--- a/app/views/projects/clusters/index.html.haml
+++ b/app/views/clusters/clusters/index.html.haml
diff --git a/app/views/projects/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml
index a38003f5750..eeeef6bd824 100644
--- a/app/views/projects/clusters/new.html.haml
+++ b/app/views/clusters/clusters/new.html.haml
@@ -19,9 +19,9 @@
.tab-content.gitlab-tab-content
.tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' }
- = render 'projects/clusters/gcp/header'
+ = render 'clusters/clusters/gcp/header'
- if @valid_gcp_token
- = render 'projects/clusters/gcp/form'
+ = render 'clusters/clusters/gcp/form'
- elsif @authorize_url
.signin-with-google
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url)
@@ -32,5 +32,5 @@
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
.tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' }
- = render 'projects/clusters/user/header'
- = render 'projects/clusters/user/form'
+ = render 'clusters/clusters/user/header'
+ = render 'clusters/clusters/user/form'
diff --git a/app/views/projects/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index c79d3f67706..7ea85fe43d6 100644
--- a/app/views/projects/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -1,25 +1,26 @@
- @content_class = "limit-container-width" unless fluid_layout
-- add_to_breadcrumbs "Kubernetes Clusters", project_clusters_path(@project)
+- add_to_breadcrumbs "Kubernetes Clusters", clusterable.index_path
- breadcrumb_title @cluster.name
- page_title _("Kubernetes Cluster")
+- manage_prometheus_path = edit_project_service_path(@cluster.project, 'prometheus') if @project
- expanded = Rails.env.test?
-- status_path = status_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
+- status_path = clusterable.cluster_status_cluster_path(@cluster.id, format: :json) if can?(current_user, :admin_cluster, @cluster)
.edit-cluster-form.js-edit-cluster-form{ data: { status_path: status_path,
- install_helm_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :helm),
- install_ingress_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :ingress),
- install_prometheus_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :prometheus),
- install_runner_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :runner),
- install_jupyter_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :jupyter),
- install_knative_path: install_applications_namespace_project_cluster_path(@cluster.project.namespace, @cluster.project, @cluster, :knative),
+ install_helm_path: clusterable.install_applications_cluster_path(@cluster, :helm),
+ install_ingress_path: clusterable.install_applications_cluster_path(@cluster, :ingress),
+ install_prometheus_path: clusterable.install_applications_cluster_path(@cluster, :prometheus),
+ install_runner_path: clusterable.install_applications_cluster_path(@cluster, :runner),
+ install_jupyter_path: clusterable.install_applications_cluster_path(@cluster, :jupyter),
+ install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
toggle_status: @cluster.enabled? ? 'true': 'false',
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
ingress_dns_help_path: help_page_path('topics/autodevops/quick_start_guide.md', anchor: 'point-dns-at-cluster-ip'),
- manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
+ manage_prometheus_path: manage_prometheus_path } }
.js-cluster-application-notice
.flash-container
@@ -39,9 +40,9 @@
%p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content
- if @cluster.managed?
- = render 'projects/clusters/gcp/show'
+ = render 'clusters/clusters/gcp/show'
- else
- = render 'projects/clusters/user/show'
+ = render 'clusters/clusters/user/show'
%section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 54a6e685bb0..4e6232b69de 100644
--- a/app/views/projects/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,9 +1,9 @@
-= form_for @user_cluster, url: create_user_namespace_project_clusters_path(@project.namespace, @project), as: :cluster do |field|
+= form_for @user_cluster, url: clusterable.create_user_clusters_path, as: :cluster do |field|
= form_errors(@user_cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
- - if has_multiple_clusters?(@project)
+ - if has_multiple_clusters?
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
= field.text_field :environment_scope, class: 'form-control', placeholder: s_('ClusterIntegration|Environment scope')
diff --git a/app/views/projects/clusters/user/_header.html.haml b/app/views/clusters/clusters/user/_header.html.haml
index 749177fa6c1..749177fa6c1 100644
--- a/app/views/projects/clusters/user/_header.html.haml
+++ b/app/views/clusters/clusters/user/_header.html.haml
diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/clusters/clusters/user/_show.html.haml
index 5b57f7ceb7d..a871fef0240 100644
--- a/app/views/projects/clusters/user/_show.html.haml
+++ b/app/views/clusters/clusters/user/_show.html.haml
@@ -1,4 +1,4 @@
-= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
+= form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster do |field|
= form_errors(@cluster)
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
diff --git a/app/views/notify/changed_milestone_issue_email.html.haml b/app/views/notify/changed_milestone_issue_email.html.haml
new file mode 100644
index 00000000000..7d5425fc72d
--- /dev/null
+++ b/app/views/notify/changed_milestone_issue_email.html.haml
@@ -0,0 +1,3 @@
+%p
+ Milestone changed to
+ %strong= link_to(@milestone.name, @milestone_url)
diff --git a/app/views/notify/changed_milestone_issue_email.text.erb b/app/views/notify/changed_milestone_issue_email.text.erb
new file mode 100644
index 00000000000..c5fc0b61518
--- /dev/null
+++ b/app/views/notify/changed_milestone_issue_email.text.erb
@@ -0,0 +1 @@
+Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> )
diff --git a/app/views/notify/changed_milestone_merge_request_email.html.haml b/app/views/notify/changed_milestone_merge_request_email.html.haml
new file mode 100644
index 00000000000..7d5425fc72d
--- /dev/null
+++ b/app/views/notify/changed_milestone_merge_request_email.html.haml
@@ -0,0 +1,3 @@
+%p
+ Milestone changed to
+ %strong= link_to(@milestone.name, @milestone_url)
diff --git a/app/views/notify/changed_milestone_merge_request_email.text.erb b/app/views/notify/changed_milestone_merge_request_email.text.erb
new file mode 100644
index 00000000000..c5fc0b61518
--- /dev/null
+++ b/app/views/notify/changed_milestone_merge_request_email.text.erb
@@ -0,0 +1 @@
+Milestone changed to <%= @milestone.name %> ( <%= @milestone_url %> )
diff --git a/app/views/notify/removed_milestone_issue_email.html.haml b/app/views/notify/removed_milestone_issue_email.html.haml
new file mode 100644
index 00000000000..7e9205b6491
--- /dev/null
+++ b/app/views/notify/removed_milestone_issue_email.html.haml
@@ -0,0 +1,2 @@
+%p
+ Milestone removed
diff --git a/app/views/notify/removed_milestone_issue_email.text.erb b/app/views/notify/removed_milestone_issue_email.text.erb
new file mode 100644
index 00000000000..0b83ed7a4c5
--- /dev/null
+++ b/app/views/notify/removed_milestone_issue_email.text.erb
@@ -0,0 +1 @@
+Milestone removed
diff --git a/app/views/notify/removed_milestone_merge_request_email.html.haml b/app/views/notify/removed_milestone_merge_request_email.html.haml
new file mode 100644
index 00000000000..7e9205b6491
--- /dev/null
+++ b/app/views/notify/removed_milestone_merge_request_email.html.haml
@@ -0,0 +1,2 @@
+%p
+ Milestone removed
diff --git a/app/views/notify/removed_milestone_merge_request_email.text.erb b/app/views/notify/removed_milestone_merge_request_email.text.erb
new file mode 100644
index 00000000000..0b83ed7a4c5
--- /dev/null
+++ b/app/views/notify/removed_milestone_merge_request_email.text.erb
@@ -0,0 +1 @@
+Milestone removed
diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 6138914206b..19159684420 100644
--- a/app/views/shared/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -19,13 +19,13 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
- "v-bind:data-selected" => "selectedLabels",
+ ":data-selected" => "selectedLabels",
+ ":data-labels" => "issue.assignableLabelsEndpoint",
data: { toggle: "dropdown",
field_name: "issue[label_names][]",
show_no: "true",
show_any: "true",
project_id: @project&.try(:id),
- labels: labels_filter_path_with_defaults,
namespace_path: @namespace_path,
project_path: @project.try(:path) } }
%span.dropdown-toggle-text
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index f21789de37d..a66a6f4c777 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -28,6 +28,7 @@
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
- gcp_cluster:cluster_wait_for_ingress_ip_address
+- gcp_cluster:cluster_platform_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
new file mode 100644
index 00000000000..68e8335a09d
--- /dev/null
+++ b/app/workers/cluster_platform_configure_worker.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ClusterPlatformConfigureWorker
+ include ApplicationWorker
+ include ClusterQueue
+
+ 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
+ end
+
+ rescue ::Kubeclient::HttpError => err
+ Rails.logger.error "Failed to create/update Kubernetes Namespace. id: #{kubernetes_namespace.id} message: #{err.message}"
+ end
+end
diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb
index 59de7903c1c..3d5894b73ec 100644
--- a/app/workers/cluster_provision_worker.rb
+++ b/app/workers/cluster_provision_worker.rb
@@ -9,6 +9,8 @@ class ClusterProvisionWorker
cluster.provider.try do |provider|
Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp?
end
+
+ ClusterPlatformConfigureWorker.perform_async(cluster.id) if cluster.user?
end
end
end
diff --git a/changelogs/unreleased/28249-add-pagination.yml b/changelogs/unreleased/28249-add-pagination.yml
new file mode 100644
index 00000000000..df15094405a
--- /dev/null
+++ b/changelogs/unreleased/28249-add-pagination.yml
@@ -0,0 +1,5 @@
+---
+title: Adds pagination to pipelines table in merge request page
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/51620-cannot-add-label-to-issue-from-board.yml b/changelogs/unreleased/51620-cannot-add-label-to-issue-from-board.yml
new file mode 100644
index 00000000000..9e99779d352
--- /dev/null
+++ b/changelogs/unreleased/51620-cannot-add-label-to-issue-from-board.yml
@@ -0,0 +1,4 @@
+title: Make Issue Board sidebar show project-specific labels based on selected Issue
+merge_request: 22475
+author:
+type: fixed
diff --git a/changelogs/unreleased/51716-create-kube-namespace.yml b/changelogs/unreleased/51716-create-kube-namespace.yml
new file mode 100644
index 00000000000..851e19c0a38
--- /dev/null
+++ b/changelogs/unreleased/51716-create-kube-namespace.yml
@@ -0,0 +1,5 @@
+---
+title: Extend RBAC by having a service account restricted to project's namespace
+merge_request: 22011
+author:
+type: other
diff --git a/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml b/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml
new file mode 100644
index 00000000000..052ef70c41a
--- /dev/null
+++ b/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug when links in tabs of the labels index pages ends with .html
+merge_request: 22716
+author:
+type: fixed
diff --git a/changelogs/unreleased/ab-45608-stuck-mr-query.yml b/changelogs/unreleased/ab-45608-stuck-mr-query.yml
new file mode 100644
index 00000000000..3b64534e480
--- /dev/null
+++ b/changelogs/unreleased/ab-45608-stuck-mr-query.yml
@@ -0,0 +1,5 @@
+---
+title: Add index to find stuck merge requests.
+merge_request: 22749
+author:
+type: performance
diff --git a/changelogs/unreleased/add-scheduled-flag-to-job-entity.yml b/changelogs/unreleased/add-scheduled-flag-to-job-entity.yml
new file mode 100644
index 00000000000..a80b5a931b9
--- /dev/null
+++ b/changelogs/unreleased/add-scheduled-flag-to-job-entity.yml
@@ -0,0 +1,5 @@
+---
+title: Add scheduled flag to job entity
+merge_request: 22710
+author:
+type: other
diff --git a/changelogs/unreleased/ccr-51520_change_milestone_email.yml b/changelogs/unreleased/ccr-51520_change_milestone_email.yml
new file mode 100644
index 00000000000..ce4beba2c5f
--- /dev/null
+++ b/changelogs/unreleased/ccr-51520_change_milestone_email.yml
@@ -0,0 +1,5 @@
+---
+title: Add email for milestone change
+merge_request: 22279
+author:
+type: added
diff --git a/changelogs/unreleased/gl-ui-progress-bar.yml b/changelogs/unreleased/gl-ui-progress-bar.yml
new file mode 100644
index 00000000000..1e584dacd6f
--- /dev/null
+++ b/changelogs/unreleased/gl-ui-progress-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Remove gitlab-ui's progress bar from global
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/kinolaev-master-patch-91872.yml b/changelogs/unreleased/kinolaev-master-patch-91872.yml
new file mode 100644
index 00000000000..053e9101e39
--- /dev/null
+++ b/changelogs/unreleased/kinolaev-master-patch-91872.yml
@@ -0,0 +1,5 @@
+---
+title: Change HELM_HOST in Auto-DevOps template to work behind proxy
+merge_request: 22596
+author: Sergej Nikolaev <kinolaev@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/remove-ci_enable_scheduled_build-feature-flag.yml b/changelogs/unreleased/remove-ci_enable_scheduled_build-feature-flag.yml
new file mode 100644
index 00000000000..ce52a487551
--- /dev/null
+++ b/changelogs/unreleased/remove-ci_enable_scheduled_build-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove `ci_enable_scheduled_build` feature flag
+merge_request: 22742
+author:
+type: other
diff --git a/changelogs/unreleased/replace-tooltip-in-markdown-component.yml b/changelogs/unreleased/replace-tooltip-in-markdown-component.yml
new file mode 100644
index 00000000000..5047e75c06a
--- /dev/null
+++ b/changelogs/unreleased/replace-tooltip-in-markdown-component.yml
@@ -0,0 +1,5 @@
+---
+title: Replace tooltip in markdown component with gl-tooltip
+merge_request: 21989
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/security-kubeclient-ssrf.yml b/changelogs/unreleased/security-kubeclient-ssrf.yml
new file mode 100644
index 00000000000..45fc41029fc
--- /dev/null
+++ b/changelogs/unreleased/security-kubeclient-ssrf.yml
@@ -0,0 +1,5 @@
+---
+title: Monkey kubeclient to not follow any redirects.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-optimize-mr-commit-sha-lookup.yml b/changelogs/unreleased/sh-optimize-mr-commit-sha-lookup.yml
new file mode 100644
index 00000000000..bea73f8d329
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-mr-commit-sha-lookup.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize merge request refresh by using the database to check commit SHAs
+merge_request: 22731
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-reload-diffs-service.yml b/changelogs/unreleased/sh-optimize-reload-diffs-service.yml
new file mode 100644
index 00000000000..422102560ed
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-reload-diffs-service.yml
@@ -0,0 +1,5 @@
+---
+title: Significantly cut memory usage and SQL queries when reloading diffs
+merge_request: 22725
+author:
+type: performance
diff --git a/changelogs/unreleased/top_level_clusters_controller.yml b/changelogs/unreleased/top_level_clusters_controller.yml
new file mode 100644
index 00000000000..1fe1d048de4
--- /dev/null
+++ b/changelogs/unreleased/top_level_clusters_controller.yml
@@ -0,0 +1,6 @@
+---
+title: Change to top level controller for clusters so that we can use it for project
+ clusters (now) and group clusters (later)
+merge_request: 22438
+author:
+type: other
diff --git a/config/initializers/8_metrics.rb b/config/initializers/8_metrics.rb
index c8d261d415e..468f80939d7 100644
--- a/config/initializers/8_metrics.rb
+++ b/config/initializers/8_metrics.rb
@@ -98,7 +98,11 @@ end
# check: https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class
#
# Related issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/33587
-if Gitlab::Metrics.enabled? && !Rails.env.test?
+#
+# In development mode, we turn off eager loading when we're running
+# `rails generate migration` because eager loading short-circuits the
+# loading of our custom migration templates.
+if Gitlab::Metrics.enabled? && !Rails.env.test? && !(Rails.env.development? && defined?(Rails::Generators))
require 'pathname'
require 'influxdb'
require 'connection_pool'
diff --git a/config/initializers/kubeclient.rb b/config/initializers/kubeclient.rb
index 7f115268b37..2d9f439fdc0 100644
--- a/config/initializers/kubeclient.rb
+++ b/config/initializers/kubeclient.rb
@@ -13,4 +13,25 @@ class Kubeclient::Client
ns_prefix = build_namespace_prefix(namespace)
rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url
end
+
+ # Monkey patch to set `max_redirects: 0`, so that kubeclient
+ # does not follow redirects and expose internal services.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/53158
+ def create_rest_client(path = nil)
+ path ||= @api_endpoint.path
+ options = {
+ ssl_ca_file: @ssl_options[:ca_file],
+ ssl_cert_store: @ssl_options[:cert_store],
+ verify_ssl: @ssl_options[:verify_ssl],
+ ssl_client_cert: @ssl_options[:client_cert],
+ ssl_client_key: @ssl_options[:client_key],
+ proxy: @http_proxy_uri,
+ user: @auth_options[:username],
+ password: @auth_options[:password],
+ open_timeout: @timeouts[:open],
+ read_timeout: @timeouts[:read],
+ max_redirects: 0
+ }
+ RestClient::Resource.new(@api_endpoint.merge(path).to_s, options)
+ end
end
diff --git a/config/routes.rb b/config/routes.rb
index 37c7f98ec98..d2d91647d0b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -84,6 +84,23 @@ Rails.application.routes.draw do
draw :instance_statistics
end
+ concern :clusterable do
+ resources :clusters, only: [:index, :new, :show, :update, :destroy] do
+ collection do
+ post :create_user
+ post :create_gcp
+ end
+
+ member do
+ scope :applications do
+ post '/:application', to: 'clusters/applications#create', as: :install_applications
+ end
+
+ get :cluster_status, format: :json
+ end
+ end
+ end
+
draw :api
draw :sidekiq
draw :help
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 73c46f72168..387d2363552 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -206,20 +206,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :clusters, except: [:edit, :create] do
- collection do
- post :create_gcp
- post :create_user
- end
-
- member do
- get :status, format: :json
-
- scope :applications do
- post '/:application', to: 'clusters/applications#create', as: :install_applications
- end
- end
- end
+ concerns :clusterable
resources :environments, except: [:destroy] do
member do
diff --git a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb
index 8fc558be733..b7b346cb10e 100644
--- a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb
+++ b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb
@@ -45,7 +45,7 @@ class CreateMissingNamespaceForInternalUsers < ActiveRecord::Migration
connection.exec_query(query).present?
end
- insert_query = "INSERT INTO namespaces(owner_id, path, name) VALUES(#{user_id}, '#{path}', '#{path}')"
+ insert_query = "INSERT INTO namespaces(owner_id, path, name, created_at, updated_at) VALUES(#{user_id}, '#{path}', '#{path}', NOW(), NOW())"
namespace_id = connection.insert_sql(insert_query)
create_route(namespace_id)
@@ -57,7 +57,7 @@ class CreateMissingNamespaceForInternalUsers < ActiveRecord::Migration
row = connection.exec_query("SELECT id, path FROM namespaces WHERE id=#{namespace_id}").first
id, path = row.values_at('id', 'path')
- execute("INSERT INTO routes(source_id, source_type, path, name) VALUES(#{id}, 'Namespace', '#{path}', '#{path}')")
+ execute("INSERT INTO routes(source_id, source_type, path, name, created_at, updated_at) VALUES(#{id}, 'Namespace', '#{path}', '#{path}', NOW(), NOW())")
end
def set_notification_email(user_id)
diff --git a/db/migrate/20181101144347_add_index_for_stuck_mr_query.rb b/db/migrate/20181101144347_add_index_for_stuck_mr_query.rb
new file mode 100644
index 00000000000..5d3ace54e5c
--- /dev/null
+++ b/db/migrate/20181101144347_add_index_for_stuck_mr_query.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+class AddIndexForStuckMrQuery < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :merge_requests, [:id, :merge_jid], where: "merge_jid IS NOT NULL and state = 'locked'"
+ end
+
+ def down
+ remove_concurrent_index :merge_requests, [:id, :merge_jid], where: "merge_jid IS NOT NULL and state = 'locked'"
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4ed585e7110..8c7b9d929db 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181031190559) do
+ActiveRecord::Schema.define(version: 20181101144347) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1304,6 +1304,7 @@ ActiveRecord::Schema.define(version: 20181031190559) do
add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
+ add_index "merge_requests", ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))", using: :btree
add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
add_index "merge_requests", ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)", using: :btree
add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 4ea35a30bbf..2f5efbe84d9 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -1,6 +1,7 @@
# Migrating projects to a GitLab instance
-1. [From Bitbucket.org](bitbucket.md)
+1. [From Bitbucket Cloud (aka bitbucket.org)](bitbucket.md)
+1. [From Bitbucket Server (aka Stash)](bitbucket_server.md)
1. [From ClearCase](clearcase.md)
1. [From CVS](cvs.md)
1. [From FogBugz](fogbugz.md)
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 9e41038e02e..c590ac4b0ba 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -92,12 +92,16 @@ In most of the below cases, the notification will be sent to:
| Reassign issue | The above, plus the old assignee |
| Reopen issue | |
| Due issue | Participants and Custom notification level with this event selected |
+| Change milestone issue | Subscribers, participants mentioned, and Custom notification level with this event selected |
+| Remove milestone issue | Subscribers, participants mentioned, and Custom notification level with this event selected |
| New merge request | |
| Push to merge request | Participants and Custom notification level with this event selected |
| Reassign merge request | The above, plus the old assignee |
| Close merge request | |
| Reopen merge request | |
| Merge merge request | |
+| Change milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
+| Remove milestone merge request | Subscribers, participants mentioned, and Custom notification level with this event selected |
| New comment | The above, plus anyone mentioned by `@username` in the comment, with notification level "Mention" or higher |
| Failed pipeline | The author of the pipeline |
| Successful pipeline | The author of the pipeline, if they have the custom notification setting for successful pipelines set |
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index db48b187e5e..734af5eba59 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -823,7 +823,7 @@ rollout 100%:
function initialize_tiller() {
echo "Checking Tiller..."
- export HELM_HOST=":44134"
+ export HELM_HOST="localhost:44134"
tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
echo "Tiller is listening on ${HELM_HOST}"
diff --git a/lib/gitlab/cluster/puma_worker_killer_initializer.rb b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
index 331c39f7d6b..4ed9a9a02ab 100644
--- a/lib/gitlab/cluster/puma_worker_killer_initializer.rb
+++ b/lib/gitlab/cluster/puma_worker_killer_initializer.rb
@@ -11,7 +11,11 @@ module Gitlab
# Importantly RAM is for _all_workers (ie, the cluster),
# not each worker as is the case with GITLAB_UNICORN_MEMORY_MAX
worker_count = puma_options[:workers] || 1
- config.ram = worker_count * puma_per_worker_max_memory_mb
+ # The Puma Worker Killer checks the total RAM used by both the master
+ # and worker processes. Bump the limits to N+1 instead of N workers
+ # to account for this:
+ # https://github.com/schneems/puma_worker_killer/blob/v0.1.0/lib/puma_worker_killer/puma_memory.rb#L57
+ config.ram = (worker_count + 1) * puma_per_worker_max_memory_mb
config.frequency = 20 # seconds
diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb
index b79ff771a2b..2ad6fe8449d 100644
--- a/lib/gitlab/diff/file_collection/base.rb
+++ b/lib/gitlab/diff/file_collection/base.rb
@@ -17,7 +17,6 @@ module Gitlab
@diffable = diffable
@include_stats = diff_options.delete(:include_stats)
- @diffs = diffable.raw_diffs(diff_options)
@project = project
@diff_options = diff_options
@diff_refs = diff_refs
@@ -25,8 +24,12 @@ module Gitlab
@repository = project.repository
end
+ def diffs
+ @diffs ||= diffable.raw_diffs(diff_options)
+ end
+
def diff_files
- @diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) }
+ @diff_files ||= diffs.decorate! { |diff| decorate_diff!(diff) }
end
def diff_file_with_old_path(old_path)
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 4a1bdf34c3e..1cd4f9e17b7 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -2,6 +2,7 @@ module Gitlab
module Kubernetes
module Helm
HELM_VERSION = '2.7.2'.freeze
+ KUBECTL_VERSION = '1.11.0'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
diff --git a/lib/gitlab/kubernetes/helm/base_command.rb b/lib/gitlab/kubernetes/helm/base_command.rb
index 1a928fb351f..008cba9d33c 100644
--- a/lib/gitlab/kubernetes/helm/base_command.rb
+++ b/lib/gitlab/kubernetes/helm/base_command.rb
@@ -11,20 +11,6 @@ module Gitlab
def generate_script
<<~HEREDOC
set -eo pipefail
- ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2)
- echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories
- echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories
- apk add -U wget ca-certificates openssl git >/dev/null
- wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
- mv /tmp/linux-amd64/helm /usr/bin/
-
- wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
- apk add glibc-2.28-r0.apk > /dev/null
- rm glibc-2.28-r0.apk
- wget -q https://storage.googleapis.com/kubernetes-release/release/v1.11.0/bin/linux/amd64/kubectl
- chmod +x kubectl
- mv kubectl /usr/bin/
HEREDOC
end
diff --git a/lib/gitlab/kubernetes/helm/pod.rb b/lib/gitlab/kubernetes/helm/pod.rb
index 95192b11c0d..e9c621d96f0 100644
--- a/lib/gitlab/kubernetes/helm/pod.rb
+++ b/lib/gitlab/kubernetes/helm/pod.rb
@@ -25,7 +25,7 @@ module Gitlab
def container_specification
{
name: 'helm',
- image: 'alpine:3.6',
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/#{Gitlab::Kubernetes::Helm::HELM_VERSION}-kube-#{Gitlab::Kubernetes::Helm::KUBECTL_VERSION}",
env: generate_pod_env(command),
command: %w(/bin/sh),
args: %w(-c $(COMMAND_SCRIPT))
diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb
index 4f3ee040bf2..cb0cb42d007 100644
--- a/lib/gitlab/kubernetes/role_binding.rb
+++ b/lib/gitlab/kubernetes/role_binding.rb
@@ -3,9 +3,8 @@
module Gitlab
module Kubernetes
class RoleBinding
- attr_reader :role_name, :namespace, :service_account_name
-
- def initialize(role_name:, namespace:, service_account_name:)
+ def initialize(name:, role_name:, namespace:, service_account_name:)
+ @name = name
@role_name = role_name
@namespace = namespace
@service_account_name = service_account_name
@@ -21,14 +20,16 @@ module Gitlab
private
+ attr_reader :name, :role_name, :namespace, :service_account_name
+
def metadata
- { name: "gitlab-#{namespace}", namespace: namespace }
+ { name: name, namespace: namespace }
end
def role_ref
{
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'Role',
+ kind: 'ClusterRole',
name: role_name
}
end
diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb
index e28a00c545b..75438b77bf3 100644
--- a/qa/qa/factory/base.rb
+++ b/qa/qa/factory/base.rb
@@ -100,7 +100,7 @@ module QA
msg = [prefix]
msg << "Built a #{name}"
msg << "as a dependency of #{parents.last}" if parents.any?
- msg << "via #{method} with args #{args}"
+ msg << "via #{method}"
yield.tap do
msg << "in #{Time.now - start} seconds"
diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb
index 18c16ca6db7..939f912ea85 100644
--- a/qa/qa/page/project/operations/kubernetes/add.rb
+++ b/qa/qa/page/project/operations/kubernetes/add.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class Add < Page::Base
- view 'app/views/projects/clusters/new.html.haml' do
+ view 'app/views/clusters/clusters/new.html.haml' do
element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index f8e026b4405..f3ab636ecc1 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class AddExisting < Page::Base
- view 'app/views/projects/clusters/user/_form.html.haml' do
+ view 'app/views/clusters/clusters/user/_form.html.haml' do
element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern
element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 312b459ac89..67a74af1cd2 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -4,7 +4,7 @@ module QA
module Operations
module Kubernetes
class Index < Page::Base
- view 'app/views/projects/clusters/_empty_state.html.haml' do
+ view 'app/views/clusters/clusters/_empty_state.html.haml' do
element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
index e9584a27d63..990eba76460 100644
--- a/qa/spec/factory/base_spec.rb
+++ b/qa/spec/factory/base_spec.rb
@@ -81,8 +81,8 @@ describe QA::Factory::Base do
stub_env('QA_DEBUG', 'true')
expect(factory).to receive(:fabricate_via_api!).and_return(location)
- expect { subject.fabricate_via_api!(factory: factory, parents: []) }
- .to output(/==> Built a MyFactory via api with args \[\] in [\d\w\.\-]+/)
+ expect { subject.fabricate_via_api!('something', factory: factory, parents: []) }
+ .to output(/==> Built a MyFactory via api in [\d\.\-e]+ seconds+/)
.to_stdout
end
end
@@ -108,7 +108,7 @@ describe QA::Factory::Base do
stub_env('QA_DEBUG', 'true')
expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) }
- .to output(/==> Built a MyFactory via browser_ui with args \["something"\] in [\d\w\.\-]+/)
+ .to output(/==> Built a MyFactory via browser_ui in [\d\.\-e]+ seconds+/)
.to_stdout
end
end
diff --git a/spec/controllers/projects/clusters/applications_controller_spec.rb b/spec/controllers/projects/clusters/applications_controller_spec.rb
index 9e17e392d3d..8106453a775 100644
--- a/spec/controllers/projects/clusters/applications_controller_spec.rb
+++ b/spec/controllers/projects/clusters/applications_controller_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::Clusters::ApplicationsController do
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 9201332c5c8..04aece26590 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Projects::ClustersController do
include AccessMatchersForController
include GoogleApi::CloudPlatformHelpers
+ include KubernetesHelpers
set(:project) { create(:project) }
@@ -218,9 +221,9 @@ describe Projects::ClustersController do
describe 'security' do
before do
allow_any_instance_of(described_class)
- .to receive(:token_in_session).and_return('token')
+ .to receive(:token_in_session).and_return('token')
allow_any_instance_of(described_class)
- .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
+ .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
.to receive(:projects_zones_clusters_create) do
OpenStruct.new(
@@ -307,6 +310,11 @@ describe Projects::ClustersController do
end
describe 'security' do
+ before do
+ allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
+ end
+
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
@@ -318,14 +326,15 @@ describe Projects::ClustersController do
end
end
- describe 'GET status' do
+ describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
def go
- get :status, namespace_id: project.namespace,
- project_id: project,
- id: cluster,
- format: :json
+ get :cluster_status,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: cluster,
+ format: :json
end
describe 'functionality' do
@@ -359,9 +368,10 @@ describe Projects::ClustersController do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go
- get :show, namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ get :show,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
end
describe 'functionality' do
@@ -401,13 +411,18 @@ describe Projects::ClustersController do
end
def go(format: :html)
- put :update, params.merge(namespace_id: project.namespace,
- project_id: project,
+ put :update, params.merge(namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
id: cluster,
format: format
)
end
+ before do
+ allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
+ end
+
context 'when cluster is provided by GCP' do
it "updates and redirects back to show page" do
go
@@ -530,9 +545,10 @@ describe Projects::ClustersController do
let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
def go
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: cluster
+ delete :destroy,
+ namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
end
describe 'functionality' do
@@ -591,4 +607,10 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:external) }
end
end
+
+ context 'no project_id param' do
+ it 'does not respond to any action without project_id param' do
+ expect { get :index }.to raise_error(ActionController::UrlGenerationError)
+ end
+ end
end
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 9e149bc4c3c..e34fdee62d6 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -356,6 +356,7 @@ describe Projects::CommitController do
expect(response).to be_ok
expect(JSON.parse(response.body)['pipelines']).not_to be_empty
expect(JSON.parse(response.body)['count']['all']).to eq 1
+ expect(response).to include_pagination_headers
end
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 7463586621c..dafff4ee405 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -563,6 +563,7 @@ describe Projects::MergeRequestsController do
it 'responds with serialized pipelines' do
expect(json_response['pipelines']).not_to be_empty
expect(json_response['count']['all']).to eq 1
+ expect(response).to include_pagination_headers
end
end
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
index 6fdada75a3d..3f10f0ecc74 100644
--- a/spec/factories/clusters/kubernetes_namespaces.rb
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -2,8 +2,18 @@
FactoryBot.define do
factory :cluster_kubernetes_namespace, class: Clusters::KubernetesNamespace do
- cluster
- project
- cluster_project
+ association :cluster, :project, :provided_by_gcp
+ namespace { |n| "environment#{n}" }
+
+ after(:build) do |kubernetes_namespace|
+ cluster_project = kubernetes_namespace.cluster.cluster_project
+
+ kubernetes_namespace.project = cluster_project.project
+ kubernetes_namespace.cluster_project = cluster_project
+ end
+
+ trait :with_token do
+ service_account_token { Faker::Lorem.characters(10) }
+ end
end
end
diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb
new file mode 100644
index 00000000000..9f597efa7b7
--- /dev/null
+++ b/spec/features/groups/board_sidebar_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Group Issue Boards', :js do
+ include BoardHelpers
+
+ let(:group) { create(:group) }
+ let(:user) { create(:group_member, user: create(:user), group: group ).user }
+ let!(:project_1) { create(:project, :public, group: group) }
+ let!(:project_2) { create(:project, :public, group: group) }
+ let!(:project_1_label) { create(:label, project: project_1, name: 'Development 1') }
+ let!(:project_2_label) { create(:label, project: project_2, name: 'Development 2') }
+ let!(:group_label) { create(:group_label, title: 'Bug', description: 'Fusce consequat', group: group) }
+ let!(:issue_1) { create(:labeled_issue, project: project_1, relative_position: 1) }
+ let!(:issue_2) { create(:labeled_issue, project: project_2, relative_position: 2) }
+ let(:board) { create(:board, group: group) }
+ let!(:list) { create(:list, board: board, label: project_1_label, position: 0) }
+ let(:card) { find('.board:nth-child(1)').first('.board-card') }
+
+ before do
+ sign_in(user)
+
+ visit group_board_path(group, board)
+ wait_for_requests
+ end
+
+ context 'labels' do
+ it 'only shows valid labels for the issue project and group' do
+ click_card(card)
+
+ page.within('.labels') do
+ click_link 'Edit'
+
+ wait_for_requests
+
+ page.within('.selectbox') do
+ expect(page).to have_content(project_1_label.title)
+ expect(page).to have_content(group_label.title)
+ expect(page).not_to have_content(project_2_label.title)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 8b92b9fc869..3d17eb3a73a 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -130,6 +130,7 @@ describe 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do
before do
+ allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
page.within('#js-cluster-details') { click_button 'Save changes' }
end
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 9ae1dba60b5..250c964cc32 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -9,7 +9,9 @@ describe 'User Cluster', :js do
before do
project.add_maintainer(user)
gitlab_sign_in(user)
+
allow(Projects::ClustersController).to receive(:STATUS_POLLING_INTERVAL) { 100 }
+ allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
end
context 'when user does not have a cluster and visits cluster index page' do
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index 8833825e3fb..4878df43d28 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -15,6 +15,7 @@
"relative_position": { "type": "integer" },
"issue_sidebar_endpoint": { "type": "string" },
"toggle_subscription_endpoint": { "type": "string" },
+ "assignable_labels_endpoint": { "type": "string" },
"reference_path": { "type": "string" },
"real_path": { "type": "string" },
"project": {
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index a2cda58e5d2..c04f679bcf0 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -211,4 +211,29 @@ describe LabelsHelper do
end
end
end
+
+ describe 'labels_filter_path' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project) }
+
+ it 'links to the dashboard labels page' do
+ expect(labels_filter_path).to eq(dashboard_labels_path)
+ end
+
+ it 'links to the group labels page' do
+ assign(:group, group)
+
+ expect(helper.labels_filter_path).to eq(group_labels_path(group))
+ end
+
+ it 'links to the project labels page' do
+ assign(:project, project)
+
+ expect(helper.labels_filter_path).to eq(project_labels_path(project))
+ end
+
+ it 'supports json format' do
+ expect(labels_filter_path(format: :json)).to eq(dashboard_labels_path(format: :json))
+ end
+ end
end
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index b797cc44ae7..04c8ab44405 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -72,6 +72,29 @@ describe('Pipelines table in Commits and Merge requests', function() {
done();
}, 0);
});
+
+ describe('with pagination', () => {
+ it('should make an API request when using pagination', done => {
+ setTimeout(() => {
+ spyOn(vm, 'updateContent');
+
+ vm.store.state.pageInfo = {
+ page: 1,
+ total: 10,
+ perPage: 2,
+ nextPage: 2,
+ totalPages: 5,
+ };
+
+ vm.$nextTick(() => {
+ vm.$el.querySelector('.js-next-button a').click();
+
+ expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
+ done();
+ });
+ });
+ });
+ });
});
describe('pipeline badge counts', () => {
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index b333b334f36..c92bc92c42d 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'should generate the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('alpine:3.6')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.7.2-kube-1.11.0')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index da3f5d27b25..a1a59533bfb 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_ref) do
{
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'Role',
+ kind: 'ClusterRole',
name: role_name
}
end
@@ -35,6 +35,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
subject do
described_class.new(
+ name: "gitlab-#{namespace}",
role_name: role_name,
namespace: namespace,
service_account_name: service_account_name
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 65e06f27f35..13a4aaa8936 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -216,14 +216,6 @@ describe Ci::Build do
let(:build) { create(:ci_build, :created, :schedulable, project: project) }
it { expect(subject).to be_truthy }
-
- context 'when feature flag is diabled' do
- before do
- stub_feature_flags(ci_enable_scheduled_build: false)
- end
-
- it { expect(subject).to be_falsy }
- end
end
context 'when build is not schedulable' do
@@ -327,10 +319,6 @@ describe Ci::Build do
describe '#enqueue_scheduled' do
subject { build.enqueue_scheduled }
- before do
- stub_feature_flags(ci_enable_scheduled_build: true)
- end
-
context 'when build is scheduled and the right time has not come yet' do
let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index f9776acd4c8..48ba163b38c 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -109,14 +109,20 @@ describe Clusters::Applications::Prometheus do
end
context 'cluster has kubeclient' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url }
let(:kube_client) { subject.cluster.kubeclient.core_client }
- subject { create(:clusters_applications_prometheus) }
+ subject { create(:clusters_applications_prometheus, cluster: cluster) }
before do
subject.cluster.platform_kubernetes.namespace = 'a-namespace'
- stub_kubeclient_discover(subject.cluster.platform_kubernetes.api_url)
+ stub_kubeclient_discover(cluster.platform_kubernetes.api_url)
+
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ cluster_project: cluster.cluster_project,
+ project: cluster.cluster_project.project)
end
it 'creates proxy prometheus rest client' do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 2473895a5d4..10b9ca1a778 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -16,6 +16,7 @@ describe Clusters::Cluster do
it { is_expected.to have_one(:application_runner) }
it { is_expected.to have_many(:kubernetes_namespaces) }
it { is_expected.to have_one(:kubernetes_namespace) }
+ it { is_expected.to have_one(:cluster_project) }
it { is_expected.to delegate_method(:status).to(:provider) }
it { is_expected.to delegate_method(:status_reason).to(:provider) }
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index dea58fa26c7..0dfeea5cd2f 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -10,23 +10,15 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
describe 'namespace uniqueness validation' do
let(:cluster_project) { create(:cluster_project) }
-
- let(:kubernetes_namespace) do
- build(:cluster_kubernetes_namespace,
- cluster: cluster_project.cluster,
- project: cluster_project.project,
- cluster_project: cluster_project)
- end
+ let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
subject { kubernetes_namespace }
context 'when cluster is using the namespace' do
before do
create(:cluster_kubernetes_namespace,
- cluster: cluster_project.cluster,
- project: cluster_project.project,
- cluster_project: cluster_project,
- namespace: kubernetes_namespace.namespace)
+ cluster: kubernetes_namespace.cluster,
+ namespace: 'my-namespace')
end
it { is_expected.not_to be_valid }
@@ -37,48 +29,79 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end
end
- describe '#set_namespace_and_service_account_to_default' do
- let(:cluster) { platform.cluster }
- let(:cluster_project) { create(:cluster_project, cluster: cluster) }
- let(:kubernetes_namespace) do
- create(:cluster_kubernetes_namespace,
- cluster: cluster_project.cluster,
- project: cluster_project.project,
- cluster_project: cluster_project)
- end
+ describe '#configure_predefined_variables' do
+ let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) }
+ let(:cluster) { kubernetes_namespace.cluster }
+ let(:platform) { kubernetes_namespace.platform_kubernetes }
- describe 'namespace' do
- let(:platform) { create(:cluster_platform_kubernetes, namespace: namespace) }
+ subject { kubernetes_namespace.configure_predefined_credentials }
- subject { kubernetes_namespace.namespace }
+ describe 'namespace' do
+ before do
+ platform.update_column(:namespace, namespace)
+ end
context 'when platform has a namespace assigned' do
let(:namespace) { 'platform-namespace' }
it 'should copy the namespace' do
- is_expected.to eq('platform-namespace')
+ subject
+
+ expect(kubernetes_namespace.namespace).to eq('platform-namespace')
end
end
context 'when platform does not have namespace assigned' do
+ let(:project) { kubernetes_namespace.project }
let(:namespace) { nil }
+ let(:project_slug) { "#{project.path}-#{project.id}" }
- it 'should set default namespace' do
- project_slug = "#{cluster_project.project.path}-#{cluster_project.project_id}"
+ it 'should fallback to project namespace' do
+ subject
- is_expected.to eq(project_slug)
+ expect(kubernetes_namespace.namespace).to eq(project_slug)
end
end
end
describe 'service_account_name' do
- let(:platform) { create(:cluster_platform_kubernetes) }
-
- subject { kubernetes_namespace.service_account_name }
+ let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
it 'should set a service account name based on namespace' do
- is_expected.to eq("#{kubernetes_namespace.namespace}-service-account")
+ subject
+
+ expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
end
end
end
+
+ describe '#predefined_variables' do
+ let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, service_account_token: token) }
+ let(:cluster) { create(:cluster, :project, platform_kubernetes: platform) }
+ let(:platform) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
+
+ let(:api_url) { 'https://kube.domain.com' }
+ let(:ca_pem) { 'CA PEM DATA' }
+ let(:token) { 'token' }
+
+ let(:kubeconfig) do
+ config_file = expand_fixture_path('config/kubeconfig.yml')
+ config = YAML.safe_load(File.read(config_file))
+ config.dig('users', 0, 'user')['token'] = token
+ config.dig('contexts', 0, 'context')['namespace'] = kubernetes_namespace.namespace
+ config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
+ Base64.strict_encode64(ca_pem)
+
+ YAML.dump(config)
+ end
+
+ it 'sets the variables' do
+ expect(kubernetes_namespace.predefined_variables).to include(
+ { key: 'KUBE_SERVICE_ACCOUNT', value: kubernetes_namespace.service_account_name, public: true },
+ { key: 'KUBE_NAMESPACE', value: kubernetes_namespace.namespace, public: true },
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false },
+ { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true }
+ )
+ end
+ end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index e13eb554add..2bcccc8184a 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -124,9 +124,17 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
describe '#kubeclient' do
+ let(:cluster) { create(:cluster, :project) }
+ let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) }
+
subject { kubernetes.kubeclient }
- let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace') }
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: kubernetes.cluster,
+ cluster_project: kubernetes.cluster.cluster_project,
+ project: kubernetes.cluster.cluster_project.project)
+ end
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
end
@@ -186,29 +194,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
describe '#predefined_variables' do
let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
+ let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
let(:api_url) { 'https://kube.domain.com' }
let(:ca_pem) { 'CA PEM DATA' }
- let(:token) { 'token' }
-
- let(:kubeconfig) do
- config_file = expand_fixture_path('config/kubeconfig.yml')
- config = YAML.load(File.read(config_file))
- config.dig('users', 0, 'user')['token'] = token
- config.dig('contexts', 0, 'context')['namespace'] = namespace
- config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
- Base64.strict_encode64(ca_pem)
-
- YAML.dump(config)
- end
shared_examples 'setting variables' do
it 'sets the variables' do
- expect(kubernetes.predefined_variables).to include(
+ expect(kubernetes.predefined_variables(project: cluster.project)).to include(
{ key: 'KUBE_URL', value: api_url, public: true },
- { key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: namespace, public: true },
- { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
{ key: 'KUBE_CA_PEM', value: ca_pem, public: true },
{ key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
)
@@ -229,13 +222,6 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
let(:namespace) { kubernetes.actual_namespace }
it_behaves_like 'setting variables'
-
- it 'sets the KUBE_NAMESPACE' do
- kube_namespace = kubernetes.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
-
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
- end
end
end
@@ -319,4 +305,27 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to include(pods: []) }
end
end
+
+ describe '#update_kubernetes_namespace' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+ let(:platform) { cluster.platform }
+
+ context 'when namespace is updated' do
+ it 'should call ConfigureWorker' do
+ expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id).once
+
+ platform.namespace = 'new-namespace'
+ platform.save
+ end
+ end
+
+ context 'when namespace is not updated' do
+ it 'should not call ConfigureWorker' do
+ expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
+
+ platform.username = "new-username"
+ platform.save
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 90cce826b6c..47e8f04e728 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -52,9 +52,9 @@ describe MergeRequestDiff do
context 'when it was not cleaned by the system' do
it 'returns persisted diffs' do
- expect(diff).to receive(:load_diffs)
+ expect(diff).to receive(:load_diffs).and_call_original
- diff.diffs
+ diff.diffs.diff_files
end
end
@@ -76,19 +76,19 @@ describe MergeRequestDiff do
end
it 'returns persisted diffs if cannot compare with diff refs' do
- expect(diff).to receive(:load_diffs)
+ expect(diff).to receive(:load_diffs).and_call_original
diff.update!(head_commit_sha: 'invalid-sha')
- diff.diffs
+ diff.diffs.diff_files
end
it 'returns persisted diffs if diff refs does not exist' do
- expect(diff).to receive(:load_diffs)
+ expect(diff).to receive(:load_diffs).and_call_original
diff.update!(start_commit_sha: nil, base_commit_sha: nil)
- diff.diffs
+ diff.diffs.diff_files
end
end
end
@@ -211,4 +211,25 @@ describe MergeRequestDiff do
expect(diff_with_commits.commits_count).to eq(29)
end
end
+
+ describe '#commits_by_shas' do
+ let(:commit_shas) { diff_with_commits.commit_shas }
+
+ it 'returns empty if no SHAs were provided' do
+ expect(diff_with_commits.commits_by_shas([])).to be_empty
+ end
+
+ it 'returns one SHA' do
+ commits = diff_with_commits.commits_by_shas([commit_shas.first, Gitlab::Git::BLANK_SHA])
+
+ expect(commits.count).to eq(1)
+ end
+
+ it 'returns all matching SHAs' do
+ commits = diff_with_commits.commits_by_shas(commit_shas)
+
+ expect(commits.count).to eq(commit_shas.count)
+ expect(commits.map(&:sha)).to match_array(commit_shas)
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 85a4ebac66c..2eb5e39ccfd 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -552,9 +552,9 @@ describe MergeRequest do
it 'delegates to the MR diffs' do
merge_request.save
- expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options))
+ expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)).and_call_original
- merge_request.diffs(options)
+ merge_request.diffs(options).diff_files
end
end
@@ -2611,6 +2611,32 @@ describe MergeRequest do
end
end
+ describe '#includes_any_commits?' do
+ it 'returns false' do
+ expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(subject.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
+ end
+
+ it 'returns true even when there is a non-existent comit' do
+ expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA, subject.merge_request_diff.head_commit_sha])).to be_truthy
+ end
+
+ context 'unpersisted merge request' do
+ let(:new_mr) { build(:merge_request) }
+
+ it 'returns false' do
+ expect(new_mr.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(new_mr.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
+ end
+ end
+ end
+
describe '#can_allow_collaboration?' do
let(:target_project) { create(:project, :public) }
let(:source_project) { fork_project(target_project) }
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 68ab9fd08ec..9c27357ffaf 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -253,7 +253,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
end
- describe '#predefined_variables' do
+ describe '#predefined_variable' do
let(:kubeconfig) do
config_file = expand_fixture_path('config/kubeconfig.yml')
config = YAML.load(File.read(config_file))
@@ -274,7 +274,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
shared_examples 'setting variables' do
it 'sets the variables' do
- expect(subject.predefined_variables).to include(
+ expect(subject.predefined_variables(project: project)).to include(
{ key: 'KUBE_URL', value: 'https://kube.domain.com', public: true },
{ key: 'KUBE_TOKEN', value: 'token', public: false },
{ key: 'KUBE_NAMESPACE', value: namespace, public: true },
@@ -301,7 +301,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
it_behaves_like 'setting variables'
it 'sets the KUBE_NAMESPACE' do
- kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
+ kube_namespace = subject.predefined_variables(project: project).find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index d4b9a4c8cd6..d059854214f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -2405,12 +2405,24 @@ describe Project do
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
- context 'when user configured kubernetes from CI/CD > Clusters' do
+ context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes'
end
+
+ context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
+ let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace) }
+ let!(:cluster) { kubernetes_namespace.cluster }
+ let(:project) { kubernetes_namespace.project }
+
+ it 'should return token from kubernetes namespace' do
+ expect(project.deployment_variables).to include(
+ { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
+ )
+ end
+ end
end
end
diff --git a/spec/presenters/clusterable_presenter_spec.rb b/spec/presenters/clusterable_presenter_spec.rb
new file mode 100644
index 00000000000..4f4ae5e07c5
--- /dev/null
+++ b/spec/presenters/clusterable_presenter_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ describe '.fabricate' do
+ let(:project) { create(:project) }
+
+ subject { described_class.fabricate(project) }
+
+ it 'creates an object from a descendant presenter' do
+ expect(subject).to be_kind_of(ProjectClusterablePresenter)
+ end
+ end
+end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index e96dbfb73c0..7af181f37d5 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -1,7 +1,9 @@
require 'spec_helper'
describe Clusters::ClusterPresenter do
- let(:cluster) { create(:cluster, :provided_by_gcp) }
+ include Gitlab::Routing.url_helpers
+
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
subject(:presenter) do
described_class.new(cluster)
@@ -71,4 +73,14 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(false) }
end
end
+
+ describe '#show_path' do
+ subject { described_class.new(cluster).show_path }
+
+ context 'project_type cluster' do
+ let(:project) { cluster.project }
+
+ it { is_expected.to eq(project_cluster_path(project, cluster)) }
+ end
+ end
end
diff --git a/spec/presenters/project_clusterable_presenter_spec.rb b/spec/presenters/project_clusterable_presenter_spec.rb
new file mode 100644
index 00000000000..c50d90ae1e8
--- /dev/null
+++ b/spec/presenters/project_clusterable_presenter_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectClusterablePresenter do
+ include Gitlab::Routing.url_helpers
+
+ let(:presenter) { described_class.new(project) }
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ describe '#can_create_cluster?' do
+ let(:user) { create(:user) }
+
+ subject { presenter.can_create_cluster? }
+
+ before do
+ allow(presenter).to receive(:current_user).and_return(user)
+ end
+
+ context 'when user can create' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when user cannot create' do
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#index_path' do
+ subject { presenter.index_path }
+
+ it { is_expected.to eq(project_clusters_path(project)) }
+ end
+
+ describe '#new_path' do
+ subject { presenter.new_path }
+
+ it { is_expected.to eq(new_project_cluster_path(project)) }
+ end
+
+ describe '#create_user_clusters_path' do
+ subject { presenter.create_user_clusters_path }
+
+ it { is_expected.to eq(create_user_project_clusters_path(project)) }
+ end
+
+ describe '#create_gcp_clusters_path' do
+ subject { presenter.create_gcp_clusters_path }
+
+ it { is_expected.to eq(create_gcp_project_clusters_path(project)) }
+ end
+
+ describe '#cluster_status_cluster_path' do
+ subject { presenter.cluster_status_cluster_path(cluster) }
+
+ it { is_expected.to eq(cluster_status_project_cluster_path(project, cluster)) }
+ end
+
+ describe '#install_applications_cluster_path' do
+ let(:application) { :helm }
+
+ subject { presenter.install_applications_cluster_path(cluster, application) }
+
+ it { is_expected.to eq(install_applications_project_cluster_path(project, cluster, application)) }
+ end
+
+ describe '#cluster_path' do
+ subject { presenter.cluster_path(cluster) }
+
+ it { is_expected.to eq(project_cluster_path(project, cluster)) }
+ end
+end
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
index 9e2bee2ee60..ea88951ebc6 100644
--- a/spec/serializers/build_action_entity_spec.rb
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -26,6 +26,10 @@ describe BuildActionEntity do
context 'when job is scheduled' do
let(:job) { create(:ci_build, :scheduled) }
+ it 'returns scheduled' do
+ expect(subject[:scheduled]).to be_truthy
+ end
+
it 'returns scheduled_at' do
expect(subject[:scheduled_at]).to eq(job.scheduled_at)
end
diff --git a/spec/serializers/job_entity_spec.rb b/spec/serializers/job_entity_spec.rb
index 5fc27da4906..851b41a7f7e 100644
--- a/spec/serializers/job_entity_spec.rb
+++ b/spec/serializers/job_entity_spec.rb
@@ -117,6 +117,7 @@ describe JobEntity do
end
it 'contains scheduled_at' do
+ expect(subject[:scheduled]).to be_truthy
expect(subject[:scheduled_at]).to eq(job.scheduled_at)
end
end
diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb
index 9a53b32394d..704685417bb 100644
--- a/spec/services/ci/process_build_service_spec.rb
+++ b/spec/services/ci/process_build_service_spec.rb
@@ -98,47 +98,19 @@ describe Ci::ProcessBuildService, '#execute' do
let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) }
- context 'when ci_enable_scheduled_build is enabled' do
- before do
- stub_feature_flags(ci_enable_scheduled_build: true)
- end
-
- context 'when current status is success' do
- let(:current_status) { 'success' }
-
- it 'changes the build status' do
- expect { subject }.to change { build.status }.to('scheduled')
- end
- end
-
- context 'when current status is failed' do
- let(:current_status) { 'failed' }
+ context 'when current status is success' do
+ let(:current_status) { 'success' }
- it 'does not change the build status' do
- expect { subject }.to change { build.status }.to('skipped')
- end
+ it 'changes the build status' do
+ expect { subject }.to change { build.status }.to('scheduled')
end
end
- context 'when ci_enable_scheduled_build is disabled' do
- before do
- stub_feature_flags(ci_enable_scheduled_build: false)
- end
-
- context 'when current status is success' do
- let(:current_status) { 'success' }
-
- it 'changes the build status' do
- expect { subject }.to change { build.status }.to('manual')
- end
- end
-
- context 'when current status is failed' do
- let(:current_status) { 'failed' }
+ context 'when current status is failed' do
+ let(:current_status) { 'failed' }
- it 'does not change the build status' do
- expect { subject }.to change { build.status }.to('skipped')
- end
+ it 'does not change the build status' do
+ expect { subject }.to change { build.status }.to('skipped')
end
end
end
diff --git a/spec/services/ci/run_scheduled_build_service_spec.rb b/spec/services/ci/run_scheduled_build_service_spec.rb
index 2c921dac238..be2aad33ef4 100644
--- a/spec/services/ci/run_scheduled_build_service_spec.rb
+++ b/spec/services/ci/run_scheduled_build_service_spec.rb
@@ -7,10 +7,6 @@ describe Ci::RunScheduledBuildService do
subject { described_class.new(project, user).execute(build) }
- before do
- stub_feature_flags(ci_enable_scheduled_build: true)
- end
-
context 'when user can update build' do
before do
project.add_developer(user)
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 3959295c13e..274880f2c49 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -5,18 +5,43 @@ describe Clusters::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
- subject { described_class.new(user, params).execute(project: project, access_token: access_token) }
+ subject { described_class.new(user, params).execute(access_token: access_token) }
context 'when provider is gcp' do
context 'when project has no clusters' do
context 'when correct params' do
- include_context 'valid cluster create params'
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: 'gcp-project',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a',
+ legacy_abac: 'true'
+ },
+ clusterable: project
+ }
+ end
include_examples 'create cluster service success'
end
context 'when invalid params' do
- include_context 'invalid cluster create params'
+ let(:params) do
+ {
+ name: 'test-cluster',
+ provider_type: :gcp,
+ provider_gcp_attributes: {
+ gcp_project_id: '!!!!!!!',
+ zone: 'us-central1-a',
+ num_nodes: 1,
+ machine_type: 'machine_type-a'
+ },
+ clusterable: project
+ }
+ end
include_examples 'create cluster service error'
end
diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
index 25363c54255..7fbb6cf2cf5 100644
--- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb
+++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb
@@ -1,156 +1,176 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Clusters::Gcp::FinalizeCreationService do
+describe Clusters::Gcp::FinalizeCreationService, '#execute' do
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
- describe '#execute' do
- let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
- let(:provider) { cluster.provider }
- let(:platform) { cluster.platform }
- let(:gcp_project_id) { provider.gcp_project_id }
- let(:zone) { provider.zone }
- let(:cluster_name) { cluster.name }
+ let(:cluster) { create(:cluster, :project, :providing_by_gcp) }
+ let(:provider) { cluster.provider }
+ let(:platform) { cluster.platform }
+ let(:endpoint) { '111.111.111.111' }
+ let(:api_url) { 'https://' + endpoint }
+ let(:username) { 'sample-username' }
+ let(:password) { 'sample-password' }
+ let(:secret_name) { 'gitlab-token' }
+ let(:token) { 'sample-token' }
+ let(:namespace) { "#{cluster.project.path}-#{cluster.project.id}" }
- subject { described_class.new.execute(provider) }
+ subject { described_class.new.execute(provider) }
- shared_examples 'success' do
- it 'configures provider and kubernetes' do
- subject
+ shared_examples 'success' do
+ it 'configures provider and kubernetes' do
+ subject
- expect(provider).to be_created
- end
+ expect(provider).to be_created
end
- shared_examples 'error' do
- it 'sets an error to provider object' do
- subject
+ it 'properly configures database models' do
+ subject
- expect(provider.reload).to be_errored
- end
+ cluster.reload
+
+ expect(provider.endpoint).to eq(endpoint)
+ expect(platform.api_url).to eq(api_url)
+ expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert))
+ expect(platform.username).to eq(username)
+ expect(platform.password).to eq(password)
+ expect(platform.token).to eq(token)
+ end
+
+ it 'creates kubernetes namespace model' do
+ subject
+
+ kubernetes_namespace = cluster.reload.kubernetes_namespace
+ expect(kubernetes_namespace).to be_persisted
+ expect(kubernetes_namespace.namespace).to eq(namespace)
+ expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
+ expect(kubernetes_namespace.service_account_token).to be_present
end
+ end
+
+ shared_examples 'error' do
+ it 'sets an error to provider object' do
+ subject
- context 'when succeeded to fetch gke cluster info' do
- let(:endpoint) { '111.111.111.111' }
- let(:api_url) { 'https://' + endpoint }
- let(:username) { 'sample-username' }
- let(:password) { 'sample-password' }
- let(:secret_name) { 'gitlab-token' }
+ expect(provider.reload).to be_errored
+ end
+ end
+ shared_examples 'kubernetes information not successfully fetched' do
+ context 'when failed to fetch gke cluster info' do
before do
- stub_cloud_platform_get_zone_cluster(
- gcp_project_id, zone, cluster_name,
- {
- endpoint: endpoint,
- username: username,
- password: password
- }
- )
+ stub_cloud_platform_get_zone_cluster_error(provider.gcp_project_id, provider.zone, cluster.name)
end
- context 'service account and token created' do
- before do
- stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url)
- stub_kubeclient_create_secret(api_url)
- end
-
- shared_context 'kubernetes token successfully fetched' do
- let(:token) { 'sample-token' }
-
- before do
- stub_kubeclient_get_secret(
- api_url,
- {
- metadata_name: secret_name,
- token: Base64.encode64(token)
- } )
- end
- end
-
- context 'provider legacy_abac is enabled' do
- include_context 'kubernetes token successfully fetched'
-
- it_behaves_like 'success'
-
- it 'properly configures database models' do
- subject
-
- cluster.reload
-
- expect(provider.endpoint).to eq(endpoint)
- expect(platform.api_url).to eq(api_url)
- expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert).chomp)
- expect(platform.username).to eq(username)
- expect(platform.password).to eq(password)
- expect(platform).to be_abac
- expect(platform.authorization_type).to eq('abac')
- expect(platform.token).to eq(token)
- end
- end
-
- context 'provider legacy_abac is disabled' do
- before do
- provider.legacy_abac = false
- end
-
- include_context 'kubernetes token successfully fetched'
-
- context 'cluster role binding created' do
- before do
- stub_kubeclient_create_cluster_role_binding(api_url)
- end
-
- it_behaves_like 'success'
-
- it 'properly configures database models' do
- subject
-
- cluster.reload
-
- expect(provider.endpoint).to eq(endpoint)
- expect(platform.api_url).to eq(api_url)
- expect(platform.ca_cert).to eq(Base64.decode64(load_sample_cert).chomp)
- expect(platform.username).to eq(username)
- expect(platform.password).to eq(password)
- expect(platform).to be_rbac
- expect(platform.token).to eq(token)
- end
- end
- end
-
- context 'when token is empty' do
- before do
- stub_kubeclient_get_secret(api_url, token: '', metadata_name: secret_name)
- end
-
- it_behaves_like 'error'
- end
-
- context 'when failed to fetch kubernetes token' do
- before do
- stub_kubeclient_get_secret_error(api_url, secret_name)
- end
-
- it_behaves_like 'error'
- end
-
- context 'when service account fails to create' do
- before do
- stub_kubeclient_create_service_account_error(api_url)
- end
-
- it_behaves_like 'error'
- end
+ it_behaves_like 'error'
+ end
+
+ context 'when token is empty' do
+ let(:token) { '' }
+
+ it_behaves_like 'error'
+ end
+
+ context 'when failed to fetch kubernetes token' do
+ before do
+ stub_kubeclient_get_secret_error(api_url, secret_name, namespace: 'default')
end
+
+ it_behaves_like 'error'
end
- context 'when failed to fetch gke cluster info' do
+ context 'when service account fails to create' do
before do
- stub_cloud_platform_get_zone_cluster_error(gcp_project_id, zone, cluster_name)
+ stub_kubeclient_create_service_account_error(api_url, namespace: 'default')
end
it_behaves_like 'error'
end
end
+
+ shared_context 'kubernetes information successfully fetched' do
+ before do
+ stub_cloud_platform_get_zone_cluster(
+ provider.gcp_project_id, provider.zone, cluster.name,
+ {
+ endpoint: endpoint,
+ username: username,
+ password: password
+ }
+ )
+
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_get_namespace(api_url)
+ stub_kubeclient_create_namespace(api_url)
+ stub_kubeclient_create_service_account(api_url)
+ stub_kubeclient_create_secret(api_url)
+
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: secret_name,
+ token: Base64.encode64(token),
+ namespace: 'default'
+ }
+ )
+
+ stub_kubeclient_get_namespace(api_url, namespace: namespace)
+ stub_kubeclient_create_service_account(api_url, namespace: namespace)
+ stub_kubeclient_create_secret(api_url, namespace: namespace)
+
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: "#{namespace}-token",
+ token: Base64.encode64(token),
+ namespace: namespace
+ }
+ )
+ end
+ end
+
+ context 'With a legacy ABAC cluster' do
+ before do
+ provider.legacy_abac = true
+ end
+
+ include_context 'kubernetes information successfully fetched'
+
+ it_behaves_like 'success'
+
+ it 'uses ABAC authorization type' do
+ subject
+ cluster.reload
+
+ expect(platform).to be_abac
+ expect(platform.authorization_type).to eq('abac')
+ end
+
+ it_behaves_like 'kubernetes information not successfully fetched'
+ end
+
+ context 'With an RBAC cluster' do
+ before do
+ provider.legacy_abac = false
+
+ stub_kubeclient_create_cluster_role_binding(api_url)
+ stub_kubeclient_create_role_binding(api_url, namespace: namespace)
+ end
+
+ include_context 'kubernetes information successfully fetched'
+
+ it_behaves_like 'success'
+
+ it 'uses RBAC authorization type' do
+ subject
+ cluster.reload
+
+ expect(platform).to be_rbac
+ expect(platform.authorization_type).to eq('rbac')
+ end
+
+ it_behaves_like 'kubernetes information not successfully fetched'
+ end
end
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
new file mode 100644
index 00000000000..fc922218ad0
--- /dev/null
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
@@ -0,0 +1,115 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' do
+ include KubernetesHelpers
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:platform) { cluster.platform }
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:project) { cluster.project }
+ let(:cluster_project) { cluster.cluster_project }
+
+ subject do
+ described_class.new(
+ cluster: cluster,
+ kubernetes_namespace: kubernetes_namespace
+ ).execute
+ end
+
+ shared_context 'kubernetes requests' do
+ before do
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_get_namespace(api_url)
+ stub_kubeclient_create_service_account(api_url)
+ stub_kubeclient_create_secret(api_url)
+
+ stub_kubeclient_get_namespace(api_url, namespace: namespace)
+ stub_kubeclient_create_service_account(api_url, namespace: namespace)
+ stub_kubeclient_create_secret(api_url, namespace: namespace)
+
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: "#{namespace}-token",
+ token: Base64.encode64('sample-token'),
+ namespace: namespace
+ }
+ )
+ end
+ end
+
+ context 'when kubernetes namespace is not persisted' do
+ let(:namespace) { "#{project.path}-#{project.id}" }
+
+ let(:kubernetes_namespace) do
+ build(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ include_context 'kubernetes requests'
+
+ it 'creates a Clusters::KubernetesNamespace' do
+ expect do
+ subject
+ end.to change(Clusters::KubernetesNamespace, :count).by(1)
+ end
+
+ it 'creates project service account' do
+ expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
+
+ subject
+ end
+
+ it 'configures kubernetes token' do
+ subject
+
+ kubernetes_namespace.reload
+ expect(kubernetes_namespace.namespace).to eq(namespace)
+ expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
+ expect(kubernetes_namespace.encrypted_service_account_token).to be_present
+ end
+ end
+
+ context 'when there is a Kubernetes Namespace associated' do
+ let(:namespace) { 'new-namespace' }
+
+ let(:kubernetes_namespace) do
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: cluster_project.project,
+ cluster_project: cluster_project)
+ end
+
+ include_context 'kubernetes requests'
+
+ before do
+ platform.update_column(:namespace, 'new-namespace')
+ end
+
+ it 'does not create any Clusters::KubernetesNamespace' do
+ subject
+
+ expect(cluster.kubernetes_namespace).to eq(kubernetes_namespace)
+ end
+
+ it 'creates project service account' do
+ expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateServiceAccountService).to receive(:execute).once
+
+ subject
+ end
+
+ it 'updates Clusters::KubernetesNamespace' do
+ subject
+
+ kubernetes_namespace.reload
+
+ expect(kubernetes_namespace.namespace).to eq(namespace)
+ expect(kubernetes_namespace.service_account_name).to eq("#{namespace}-service-account")
+ expect(kubernetes_namespace.encrypted_service_account_token).to be_present
+ end
+ end
+end
diff --git a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
index b096f1fa4fb..588edff85d4 100644
--- a/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_service_account_service_spec.rb
@@ -1,94 +1,165 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe Clusters::Gcp::Kubernetes::CreateServiceAccountService do
include KubernetesHelpers
- let(:service) { described_class.new(kubeclient, rbac: rbac) }
+ let(:api_url) { 'http://111.111.111.111' }
+ let(:platform_kubernetes) { cluster.platform_kubernetes }
+ let(:cluster_project) { cluster.cluster_project }
+ let(:project) { cluster_project.project }
+ let(:cluster) do
+ create(:cluster,
+ :project, :provided_by_gcp,
+ platform_kubernetes: create(:cluster_platform_kubernetes, :configured))
+ end
+
+ let(:kubeclient) do
+ Gitlab::Kubernetes::KubeClient.new(
+ api_url,
+ auth_options: { username: 'admin', password: 'xxx' }
+ )
+ end
- describe '#execute' do
- let(:rbac) { false }
- let(:api_url) { 'http://111.111.111.111' }
- let(:username) { 'admin' }
- let(:password) { 'xxx' }
+ shared_examples 'creates service account and token' do
+ it 'creates a kubernetes service account' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/serviceaccounts").with(
+ body: hash_including(
+ kind: 'ServiceAccount',
+ metadata: { name: service_account_name, namespace: namespace }
+ )
+ )
+ end
- let(:kubeclient) do
- Gitlab::Kubernetes::KubeClient.new(
- api_url,
- auth_options: { username: username, password: password }
+ it 'creates a kubernetes secret' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + "/api/v1/namespaces/#{namespace}/secrets").with(
+ body: hash_including(
+ kind: 'Secret',
+ metadata: {
+ name: token_name,
+ namespace: namespace,
+ annotations: {
+ 'kubernetes.io/service-account.name': service_account_name
+ }
+ },
+ type: 'kubernetes.io/service-account-token'
+ )
)
end
+ end
+
+ before do
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_get_namespace(api_url, namespace: namespace)
+ stub_kubeclient_create_service_account(api_url, namespace: namespace )
+ stub_kubeclient_create_secret(api_url, namespace: namespace)
+ end
+
+ describe '.gitlab_creator' do
+ let(:namespace) { 'default' }
+ let(:service_account_name) { 'gitlab' }
+ let(:token_name) { 'gitlab-token' }
+
+ subject { described_class.gitlab_creator(kubeclient, rbac: rbac).execute }
+
+ context 'with ABAC cluster' do
+ let(:rbac) { false }
+
+ it_behaves_like 'creates service account and token'
+ end
- subject { service.execute }
+ context 'with RBAC cluster' do
+ let(:rbac) { true }
- context 'when params are correct' do
before do
- stub_kubeclient_discover(api_url)
- stub_kubeclient_create_service_account(api_url)
- stub_kubeclient_create_secret(api_url)
- end
+ cluster.platform_kubernetes.rbac!
- shared_examples 'creates service account and token' do
- it 'creates a kubernetes service account' do
- subject
+ stub_kubeclient_create_cluster_role_binding(api_url)
+ end
- expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/serviceaccounts').with(
- body: hash_including(
- kind: 'ServiceAccount',
- metadata: { name: 'gitlab', namespace: 'default' }
- )
- )
- end
-
- it 'creates a kubernetes secret of type ServiceAccountToken' do
- subject
-
- expect(WebMock).to have_requested(:post, api_url + '/api/v1/namespaces/default/secrets').with(
- body: hash_including(
- kind: 'Secret',
- metadata: {
- name: 'gitlab-token',
- namespace: 'default',
- annotations: {
- 'kubernetes.io/service-account.name': 'gitlab'
- }
- },
- type: 'kubernetes.io/service-account-token'
- )
+ it_behaves_like 'creates service account and token'
+
+ it 'should create a cluster role binding with cluster-admin access' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/clusterrolebindings").with(
+ body: hash_including(
+ kind: 'ClusterRoleBinding',
+ metadata: { name: 'gitlab-admin' },
+ roleRef: {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'ClusterRole',
+ name: 'cluster-admin'
+ },
+ subjects: [
+ {
+ kind: 'ServiceAccount',
+ name: service_account_name,
+ namespace: namespace
+ }
+ ]
)
- end
+ )
end
+ end
+ end
+
+ describe '.namespace_creator' do
+ let(:namespace) { "#{project.path}-#{project.id}" }
+ let(:service_account_name) { "#{namespace}-service-account" }
+ let(:token_name) { "#{namespace}-token" }
+
+ subject do
+ described_class.namespace_creator(
+ kubeclient,
+ service_account_name: service_account_name,
+ service_account_namespace: namespace,
+ rbac: rbac
+ ).execute
+ end
+
+ context 'with ABAC cluster' do
+ let(:rbac) { false }
+
+ it_behaves_like 'creates service account and token'
+ end
+
+ context 'With RBAC enabled cluster' do
+ let(:rbac) { true }
+
+ before do
+ cluster.platform_kubernetes.rbac!
- context 'abac enabled cluster' do
- it_behaves_like 'creates service account and token'
+ stub_kubeclient_create_role_binding(api_url, namespace: namespace)
end
- context 'rbac enabled cluster' do
- let(:rbac) { true }
-
- before do
- stub_kubeclient_create_cluster_role_binding(api_url)
- end
-
- it_behaves_like 'creates service account and token'
-
- it 'creates a kubernetes cluster role binding' do
- subject
-
- expect(WebMock).to have_requested(:post, api_url + '/apis/rbac.authorization.k8s.io/v1/clusterrolebindings').with(
- body: hash_including(
- kind: 'ClusterRoleBinding',
- metadata: { name: 'gitlab-admin' },
- roleRef: {
- apiGroup: 'rbac.authorization.k8s.io',
- kind: 'ClusterRole',
- name: 'cluster-admin'
- },
- subjects: [{ kind: 'ServiceAccount', namespace: 'default', name: 'gitlab' }]
- )
+ it_behaves_like 'creates service account and token'
+
+ it 'creates a namespaced role binding with edit access' do
+ subject
+
+ expect(WebMock).to have_requested(:post, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings").with(
+ body: hash_including(
+ kind: 'RoleBinding',
+ metadata: { name: "gitlab-#{namespace}", namespace: "#{namespace}" },
+ roleRef: {
+ apiGroup: 'rbac.authorization.k8s.io',
+ kind: 'ClusterRole',
+ name: 'edit'
+ },
+ subjects: [
+ {
+ kind: 'ServiceAccount',
+ name: service_account_name,
+ namespace: namespace
+ }
+ ]
)
- end
+ )
end
end
end
diff --git a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
index 2355827fa5a..4d1a6bb7b3a 100644
--- a/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/fetch_kubernetes_token_service_spec.rb
@@ -1,56 +1,48 @@
# frozen_string_literal: true
-require 'fast_spec_helper'
+require 'spec_helper'
describe Clusters::Gcp::Kubernetes::FetchKubernetesTokenService do
+ include KubernetesHelpers
+
describe '#execute' do
let(:api_url) { 'http://111.111.111.111' }
- let(:username) { 'admin' }
- let(:password) { 'xxx' }
+ let(:namespace) { 'my-namespace' }
+ let(:service_account_token_name) { 'gitlab-token' }
let(:kubeclient) do
Gitlab::Kubernetes::KubeClient.new(
api_url,
- auth_options: { username: username, password: password }
+ auth_options: { username: 'admin', password: 'xxx' }
)
end
- subject { described_class.new(kubeclient).execute }
+ subject { described_class.new(kubeclient, service_account_token_name, namespace).execute }
context 'when params correct' do
let(:decoded_token) { 'xxx.token.xxx' }
let(:token) { Base64.encode64(decoded_token) }
- let(:secret_json) do
- {
- 'metadata': {
- name: 'gitlab-token'
- },
- 'data': {
- 'token': token
- }
- }
- end
-
- before do
- allow_any_instance_of(Kubeclient::Client)
- .to receive(:get_secret).and_return(secret_json)
- end
-
context 'when gitlab-token exists' do
- let(:metadata_name) { 'gitlab-token' }
+ before do
+ stub_kubeclient_discover(api_url)
+ stub_kubeclient_get_secret(
+ api_url,
+ {
+ metadata_name: service_account_token_name,
+ namespace: namespace,
+ token: token
+ }
+ )
+ end
it { is_expected.to eq(decoded_token) }
end
context 'when gitlab-token does not exist' do
- let(:secret_json) { {} }
-
- it { is_expected.to be_nil }
- end
-
- context 'when token is nil' do
- let(:token) { nil }
+ before do
+ allow(kubeclient).to receive(:get_secret).and_raise(Kubeclient::HttpError.new(404, 'Not found', nil))
+ end
it { is_expected.to be_nil }
end
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index dcd75b6912d..a1b20c61116 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe Clusters::UpdateService do
+ include KubernetesHelpers
+
describe '#execute' do
subject { described_class.new(cluster.user, params).execute(cluster) }
@@ -34,6 +36,11 @@ describe Clusters::UpdateService do
}
end
+ before do
+ allow(ClusterPlatformConfigureWorker).to receive(:perform_async)
+ stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace')
+ end
+
it 'updates namespace' do
is_expected.to eq(true)
expect(cluster.platform.namespace).to eq('custom-namespace')
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 07aa8449a66..bd519e7f077 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -343,7 +343,42 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when the milestone change' do
+ context 'when the milestone is removed' do
+ let!(:non_subscriber) { create(:user) }
+
+ let!(:subscriber) do
+ create(:user) do |u|
+ issue.toggle_subscription(u, project)
+ project.add_developer(u)
+ end
+ end
+
+ it_behaves_like 'system notes for milestones'
+
+ it 'sends notifications for subscribers of changed milestone' do
+ issue.milestone = create(:milestone)
+
+ issue.save
+
+ perform_enqueued_jobs do
+ update_issue(milestone_id: "")
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+
+ context 'when the milestone is changed' do
+ let!(:non_subscriber) { create(:user) }
+
+ let!(:subscriber) do
+ create(:user) do |u|
+ issue.toggle_subscription(u, project)
+ project.add_developer(u)
+ end
+ end
+
it 'marks todos as done' do
update_issue(milestone: create(:milestone))
@@ -351,6 +386,15 @@ describe Issues::UpdateService, :mailer do
end
it_behaves_like 'system notes for milestones'
+
+ it 'sends notifications for subscribers of changed milestone' do
+ perform_enqueued_jobs do
+ update_issue(milestone: create(:milestone))
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
end
context 'when the labels change' do
@@ -374,7 +418,7 @@ describe Issues::UpdateService, :mailer do
let!(:non_subscriber) { create(:user) }
let!(:subscriber) do
- create(:user).tap do |u|
+ create(:user) do |u|
label.toggle_subscription(u, project)
project.add_developer(u)
end
diff --git a/spec/services/merge_requests/reload_diffs_service_spec.rb b/spec/services/merge_requests/reload_diffs_service_spec.rb
index 21f369a3818..546c9f277c5 100644
--- a/spec/services/merge_requests/reload_diffs_service_spec.rb
+++ b/spec/services/merge_requests/reload_diffs_service_spec.rb
@@ -60,6 +60,17 @@ describe MergeRequests::ReloadDiffsService, :use_clean_rails_memory_store_cachin
subject.execute
end
+
+ it 'avoids N+1 queries', :request_store do
+ current_user
+ merge_request
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ subject.execute
+ end.count
+
+ expect { subject.execute }.not_to exceed_query_limit(control_count)
+ end
end
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 55dfab81c26..1b599ba11b6 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -315,7 +315,42 @@ describe MergeRequests::UpdateService, :mailer do
end
end
- context 'when the milestone change' do
+ context 'when the milestone is removed' do
+ let!(:non_subscriber) { create(:user) }
+
+ let!(:subscriber) do
+ create(:user) do |u|
+ merge_request.toggle_subscription(u, project)
+ project.add_developer(u)
+ end
+ end
+
+ it_behaves_like 'system notes for milestones'
+
+ it 'sends notifications for subscribers of changed milestone' do
+ merge_request.milestone = create(:milestone)
+
+ merge_request.save
+
+ perform_enqueued_jobs do
+ update_merge_request(milestone_id: "")
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
+ end
+
+ context 'when the milestone is changed' do
+ let!(:non_subscriber) { create(:user) }
+
+ let!(:subscriber) do
+ create(:user) do |u|
+ merge_request.toggle_subscription(u, project)
+ project.add_developer(u)
+ end
+ end
+
it 'marks pending todos as done' do
update_merge_request({ milestone: create(:milestone) })
@@ -323,6 +358,15 @@ describe MergeRequests::UpdateService, :mailer do
end
it_behaves_like 'system notes for milestones'
+
+ it 'sends notifications for subscribers of changed milestone' do
+ perform_enqueued_jobs do
+ update_merge_request(milestone: create(:milestone))
+ end
+
+ should_email(subscriber)
+ should_not_email(non_subscriber)
+ end
end
context 'when the labels change' do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 68a361fa882..2d8da7673dc 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -13,6 +13,54 @@ describe NotificationService, :mailer do
end
end
+ shared_examples 'altered milestone notification on issue' do
+ it 'sends the email to the correct people' do
+ should_email(subscriber_to_new_milestone)
+ issue.assignees.each do |a|
+ should_email(a)
+ end
+ should_email(@u_watcher)
+ should_email(@u_guest_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_email(@subscribed_participant)
+ should_email(@watcher_and_subscriber)
+ should_not_email(@u_guest_custom)
+ should_not_email(@u_committer)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_lazy_participant)
+ should_not_email(issue.author)
+ should_not_email(@u_disabled)
+ should_not_email(@u_custom_global)
+ should_not_email(@u_mentioned)
+ end
+ end
+
+ shared_examples 'altered milestone notification on merge request' do
+ it 'sends the email to the correct people' do
+ should_email(subscriber_to_new_milestone)
+ merge_request.assignees.each do |a|
+ should_email(a)
+ end
+ should_email(@u_watcher)
+ should_email(@u_guest_watcher)
+ should_email(@u_participant_mentioned)
+ should_email(@subscriber)
+ should_email(@subscribed_participant)
+ should_email(@watcher_and_subscriber)
+ should_not_email(@u_guest_custom)
+ should_not_email(@u_committer)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_participating)
+ should_not_email(@u_lazy_participant)
+ should_not_email(merge_request.author)
+ should_not_email(@u_disabled)
+ should_not_email(@u_custom_global)
+ should_not_email(@u_mentioned)
+ end
+ end
+
shared_examples 'notifications for new mentions' do
it 'sends no emails when no new mentions are present' do
send_notifications
@@ -952,6 +1000,96 @@ describe NotificationService, :mailer do
end
end
+ describe '#removed_milestone_issue' do
+ it_behaves_like 'altered milestone notification on issue' do
+ let(:milestone) { create(:milestone, project: project, issues: [issue]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
+
+ before do
+ notification.removed_milestone_issue(issue, issue.author)
+ end
+ end
+
+ context 'confidential issues' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignees: [assignee]) }
+ let(:milestone) { create(:milestone, project: project, issues: [confidential_issue]) }
+
+ it "emails subscribers of the issue's milestone that can read the issue" do
+ project.add_developer(member)
+ project.add_guest(guest)
+
+ confidential_issue.subscribe(non_member, project)
+ confidential_issue.subscribe(author, project)
+ confidential_issue.subscribe(assignee, project)
+ confidential_issue.subscribe(member, project)
+ confidential_issue.subscribe(guest, project)
+ confidential_issue.subscribe(admin, project)
+
+ reset_delivered_emails!
+
+ notification.removed_milestone_issue(confidential_issue, @u_disabled)
+
+ should_not_email(non_member)
+ should_not_email(guest)
+ should_email(author)
+ should_email(assignee)
+ should_email(member)
+ should_email(admin)
+ end
+ end
+ end
+
+ describe '#changed_milestone_issue' do
+ it_behaves_like 'altered milestone notification on issue' do
+ let(:new_milestone) { create(:milestone, project: project, issues: [issue]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
+
+ before do
+ notification.changed_milestone_issue(issue, new_milestone, issue.author)
+ end
+ end
+
+ context 'confidential issues' do
+ let(:author) { create(:user) }
+ let(:assignee) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:member) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:confidential_issue) { create(:issue, :confidential, project: project, title: 'Confidential issue', author: author, assignees: [assignee]) }
+ let(:new_milestone) { create(:milestone, project: project, issues: [confidential_issue]) }
+
+ it "emails subscribers of the issue's milestone that can read the issue" do
+ project.add_developer(member)
+ project.add_guest(guest)
+
+ confidential_issue.subscribe(non_member, project)
+ confidential_issue.subscribe(author, project)
+ confidential_issue.subscribe(assignee, project)
+ confidential_issue.subscribe(member, project)
+ confidential_issue.subscribe(guest, project)
+ confidential_issue.subscribe(admin, project)
+
+ reset_delivered_emails!
+
+ notification.changed_milestone_issue(confidential_issue, new_milestone, @u_disabled)
+
+ should_not_email(non_member)
+ should_not_email(guest)
+ should_email(author)
+ should_email(assignee)
+ should_email(member)
+ should_email(admin)
+ end
+ end
+ end
+
describe '#close_issue' do
before do
update_custom_notification(:close_issue, @u_guest_custom, resource: project)
@@ -1304,6 +1442,28 @@ describe NotificationService, :mailer do
end
end
+ describe '#removed_milestone_merge_request' do
+ it_behaves_like 'altered milestone notification on merge request' do
+ let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+
+ before do
+ notification.removed_milestone_merge_request(merge_request, merge_request.author)
+ end
+ end
+ end
+
+ describe '#changed_milestone_merge_request' do
+ it_behaves_like 'altered milestone notification on merge request' do
+ let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+
+ before do
+ notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author)
+ end
+ end
+ end
+
describe '#merge_request_unmergeable' do
it "sends email to merge request author" do
notification.merge_request_unmergeable(merge_request)
diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb
index 5cfbce84706..361d4220c6e 100644
--- a/spec/support/shared_examples/helm_generated_script.rb
+++ b/spec/support/shared_examples/helm_generated_script.rb
@@ -3,20 +3,6 @@ shared_examples 'helm commands' do
let(:helm_setup) do
<<~EOS
set -eo pipefail
- ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2)
- echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories
- echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories
- apk add -U wget ca-certificates openssl git >/dev/null
- wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.2-linux-amd64.tar.gz | tar zxC /tmp >/dev/null
- mv /tmp/linux-amd64/helm /usr/bin/
-
- wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- wget -q https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
- apk add glibc-2.28-r0.apk > /dev/null
- rm glibc-2.28-r0.apk
- wget -q https://storage.googleapis.com/kubernetes-release/release/v1.11.0/bin/linux/amd64/kubectl
- chmod +x kubectl
- mv kubectl /usr/bin/
EOS
end
diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_platform_configure_worker_spec.rb
new file mode 100644
index 00000000000..1a7ad8923f6
--- /dev/null
+++ b/spec/workers/cluster_platform_configure_worker_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterPlatformConfigureWorker, '#execute' do
+ context 'when provider type is gcp' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+
+ it 'configures kubernetes platform' do
+ expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
+
+ context 'when provider type is user' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user) }
+
+ it 'configures kubernetes platform' do
+ expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
+
+ context 'when cluster does not exist' do
+ it 'does not provision a cluster' do
+ expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
+
+ described_class.new.perform(123)
+ end
+ end
+end
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index 8054ec11a48..0a2dfef36a4 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -14,18 +14,25 @@ describe ClusterProvisionWorker do
end
context 'when provider type is user' do
- let(:cluster) { create(:cluster, provider_type: :user) }
+ let(:cluster) { create(:cluster, :provided_by_user) }
it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
described_class.new.perform(cluster.id)
end
+
+ it 'configures kubernetes platform' do
+ expect(ClusterPlatformConfigureWorker).to receive(:perform_async).with(cluster.id)
+
+ described_class.new.perform(cluster.id)
+ end
end
context 'when cluster does not exist' do
it 'does not provision a cluster' do
expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute)
+ expect(ClusterPlatformConfigureWorker).not_to receive(:perform_async)
described_class.new.perform(123)
end