diff options
227 files changed, 2122 insertions, 619 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0e7a67f9cc1..407cd8696a2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -954,7 +954,6 @@ review: - download_gitlab_chart - ensure_namespace - install_tiller - - create_secret - install_external_dns - deploy environment: diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index 02dfe1c7d6f..71fc2ac7d80 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -9,7 +9,7 @@ import eventHub from './event_hub'; import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants'; import ClustersService from './services/clusters_service'; import ClustersStore from './stores/clusters_store'; -import applications from './components/applications.vue'; +import Applications from './components/applications.vue'; import setupToggleButtons from '../toggle_buttons'; /** @@ -31,6 +31,7 @@ export default class Clusters { installKnativePath, installPrometheusPath, managePrometheusPath, + clusterType, clusterStatus, clusterStatusReason, helpPath, @@ -67,7 +68,7 @@ export default class Clusters { initDismissableCallout('.js-cluster-security-warning'); initSettingsPanels(); setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area')); - this.initApplications(); + this.initApplications(clusterType); if (this.store.state.status !== 'created') { this.updateContainer(null, this.store.state.status, this.store.state.statusReason); @@ -79,23 +80,21 @@ export default class Clusters { } } - initApplications() { + initApplications(type) { const { store } = this; const el = document.querySelector('#js-cluster-applications'); this.applications = new Vue({ el, - components: { - applications, - }, data() { return { state: store.state, }; }, render(createElement) { - return createElement('applications', { + return createElement(Applications, { props: { + type, applications: this.state.applications, helpPath: this.state.helpPath, ingressHelpPath: this.state.ingressHelpPath, diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js deleted file mode 100644 index 789c8360124..00000000000 --- a/app/assets/javascripts/clusters/clusters_index.js +++ /dev/null @@ -1,24 +0,0 @@ -import createFlash from '~/flash'; -import { __ } from '~/locale'; -import setupToggleButtons from '~/toggle_buttons'; -import initDismissableCallout from '~/dismissable_callout'; - -import ClustersService from './services/clusters_service'; - -export default () => { - const clusterList = document.querySelector('.js-clusters-list'); - - initDismissableCallout('.gcp-signup-offer'); - - // The empty state won't have a clusterList - if (clusterList) { - setupToggleButtons(document.querySelector('.js-clusters-list'), (value, toggle) => - ClustersService.updateCluster(toggle.dataset.endpoint, { cluster: { enabled: value } }).catch( - err => { - createFlash(__('Something went wrong on our end.')); - throw err; - }, - ), - ); - } -}; diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index c7ffb470d4d..c1026d1273a 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -13,7 +13,7 @@ import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import { s__, sprintf } from '../../locale'; import applicationRow from './application_row.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; -import { APPLICATION_STATUS, INGRESS } from '../constants'; +import { CLUSTER_TYPE, APPLICATION_STATUS, INGRESS } from '../constants'; export default { components: { @@ -21,6 +21,11 @@ export default { clipboardButton, }, props: { + type: { + type: String, + required: false, + default: CLUSTER_TYPE.PROJECT, + }, applications: { type: Object, required: false, @@ -59,6 +64,9 @@ export default { prometheusLogo, }), computed: { + isProjectCluster() { + return this.type === CLUSTER_TYPE.PROJECT; + }, helmInstalled() { return ( this.applications.helm.status === APPLICATION_STATUS.INSTALLED || @@ -281,6 +289,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="prometheus" :logo-url="prometheusLogo" :title="applications.prometheus.title" @@ -299,6 +308,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="runner" :logo-url="gitlabLogo" :title="applications.runner.title" @@ -317,6 +327,7 @@ export default { </div> </application-row> <application-row + v-if="isProjectCluster" id="jupyter" :logo-url="jupyterhubLogo" :title="applications.jupyter.title" diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index d707420c845..15cf4a56138 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -1,3 +1,10 @@ +// These need to match the enum found in app/models/clusters/cluster.rb +export const CLUSTER_TYPE = { + INSTANCE: 'instance_type', + GROUP: 'group_type', + PROJECT: 'project_type', +}; + // These need to match what is returned from the server export const APPLICATION_STATUS = { NOT_INSTALLABLE: 'not_installable', diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 28a02230d89..f7b7b8f10f7 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -1,6 +1,7 @@ <script> import _ from 'underscore'; import { mapActions, mapState } from 'vuex'; +import { GlLink, GlButton } from '@gitlab-org/gitlab-ui'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; import Icon from '~/vue_shared/components/icon.vue'; @@ -21,6 +22,8 @@ export default { TriggerBlock, StagesDropdown, JobsContainer, + GlLink, + GlButton, }, mixins: [timeagoMixin], props: { @@ -115,7 +118,7 @@ export default { <strong class="inline prepend-top-8"> {{ job.name }} </strong> - <a + <gl-link v-if="job.retry_path" :class="retryButtonClass" :href="job.retry_path" @@ -123,8 +126,8 @@ export default { rel="nofollow" > {{ __('Retry') }} - </a> - <a + </gl-link> + <gl-link v-if="job.terminal_path" :href="job.terminal_path" class="js-terminal-link pull-right btn btn-primary @@ -133,8 +136,8 @@ export default { > {{ __('Debug') }} <icon name="external-link" /> - </a> - <button + </gl-link> + <gl-button :aria-label="__('Toggle Sidebar')" type="button" class="btn btn-blank gutter-toggle @@ -146,20 +149,20 @@ export default { data-hidden="true" class="fa fa-angle-double-right" ></i> - </button> + </gl-button> </div> <div v-if="job.retry_path || job.new_issue_path" class="block retry-link" > - <a + <gl-link v-if="job.new_issue_path" :href="job.new_issue_path" class="js-new-issue btn btn-success btn-inverted" > {{ __('New issue') }} - </a> - <a + </gl-link> + <gl-link v-if="job.retry_path" :href="job.retry_path" class="js-retry-job btn btn-inverted-secondary" @@ -167,7 +170,7 @@ export default { rel="nofollow" > {{ __('Retry') }} - </a> + </gl-link> </div> <div :class="{ block : renderBlock }"> <p @@ -177,9 +180,9 @@ export default { <span class="build-light-text"> {{ __('Merge Request:') }} </span> - <a :href="job.merge_request.path"> + <gl-link :href="job.merge_request.path"> !{{ job.merge_request.iid }} - </a> + </gl-link> </p> <detail-row @@ -244,14 +247,14 @@ export default { v-if="job.cancel_path" class="btn-group prepend-top-5" role="group"> - <a + <gl-link :href="job.cancel_path" class="js-cancel-job btn btn-sm btn-default" data-method="post" rel="nofollow" > {{ __('Cancel') }} - </a> + </gl-link> </div> </div> diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue index 41de4a6e85a..1e62c05b4d1 100644 --- a/app/assets/javascripts/jobs/components/trigger_block.vue +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -1,5 +1,10 @@ <script> +import { GlButton } from '@gitlab-org/gitlab-ui'; + export default { + components: { + GlButton, + }, props: { trigger: { type: Object, @@ -41,15 +46,14 @@ export default { </p> <p v-if="hasVariables"> - <button + <gl-button v-if="!areVariablesVisible" type="button" class="btn btn-default group js-reveal-variables" @click="revealVariables" > {{ __('Reveal Variables') }} - </button> - + </gl-button> </p> <dl diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index a153edd0476..c1dfa036678 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -433,7 +433,7 @@ Please check your network connection and try again.`; <div class="discussion-with-resolve-btn"> <button type="button" - class="js-vue-discussion-reply btn btn-text-field mr-2 qa-discussion-reply" + class="js-vue-discussion-reply btn btn-text-field mr-sm-2 qa-discussion-reply" title="Add a reply" @click="showReplyForm" > @@ -442,7 +442,7 @@ Please check your network connection and try again.`; <div v-if="discussion.resolvable"> <button type="button" - class="btn btn-default mx-sm-2" + class="btn btn-default mr-sm-2" @click="resolveHandler()" > <i @@ -455,7 +455,7 @@ Please check your network connection and try again.`; </div> <div v-if="discussion.resolvable" - class="btn-group discussion-actions" + class="btn-group discussion-actions ml-sm-2" role="group" > <div diff --git a/app/assets/javascripts/pages/groups/clusters/destroy/index.js b/app/assets/javascripts/pages/groups/clusters/destroy/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/destroy/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/clusters/index/index.js b/app/assets/javascripts/pages/groups/clusters/index/index.js new file mode 100644 index 00000000000..845a5f7042c --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/index/index.js @@ -0,0 +1,5 @@ +import initDismissableCallout from '~/dismissable_callout'; + +document.addEventListener('DOMContentLoaded', () => { + initDismissableCallout('.gcp-signup-offer'); +}); diff --git a/app/assets/javascripts/pages/groups/clusters/show/index.js b/app/assets/javascripts/pages/groups/clusters/show/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/show/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/clusters/update/index.js b/app/assets/javascripts/pages/groups/clusters/update/index.js new file mode 100644 index 00000000000..8001d2dd1da --- /dev/null +++ b/app/assets/javascripts/pages/groups/clusters/update/index.js @@ -0,0 +1,5 @@ +import ClustersBundle from '~/clusters/clusters_bundle'; + +document.addEventListener('DOMContentLoaded', () => { + new ClustersBundle(); // eslint-disable-line no-new +}); diff --git a/app/assets/javascripts/pages/groups/index.js b/app/assets/javascripts/pages/groups/index.js new file mode 100644 index 00000000000..bf80d8b8193 --- /dev/null +++ b/app/assets/javascripts/pages/groups/index.js @@ -0,0 +1,16 @@ +import initDismissableCallout from '~/dismissable_callout'; +import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; + +document.addEventListener('DOMContentLoaded', () => { + const { page } = document.body.dataset; + const newClusterViews = [ + 'groups:clusters:new', + 'groups:clusters:create_gcp', + 'groups:clusters:create_user', + ]; + + if (newClusterViews.indexOf(page) > -1) { + initDismissableCallout('.gcp-signup-offer'); + initGkeDropdowns(); + } +}); diff --git a/app/assets/javascripts/pages/projects/clusters/index/index.js b/app/assets/javascripts/pages/projects/clusters/index/index.js index e4b8baede58..845a5f7042c 100644 --- a/app/assets/javascripts/pages/projects/clusters/index/index.js +++ b/app/assets/javascripts/pages/projects/clusters/index/index.js @@ -1,5 +1,5 @@ -import ClustersIndex from '~/clusters/clusters_index'; +import initDismissableCallout from '~/dismissable_callout'; document.addEventListener('DOMContentLoaded', () => { - new ClustersIndex(); // eslint-disable-line no-new + initDismissableCallout('.gcp-signup-offer'); }); diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue index c5a45afc634..8a0259ed5a5 100644 --- a/app/assets/javascripts/pipelines/components/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/empty_state.vue @@ -1,6 +1,11 @@ <script> +import { GlButton } from '@gitlab-org/gitlab-ui'; + export default { name: 'PipelinesEmptyState', + components: { + GlButton, + }, props: { helpPagePath: { type: String, @@ -41,12 +46,13 @@ export default { </p> <div class="text-center"> - <a + <gl-button :href="helpPagePath" - class="btn btn-primary js-get-started-pipelines" + variant="primary" + class="js-get-started-pipelines" > {{ s__('Pipelines|Get started with Pipelines') }} - </a> + </gl-button> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue index efb80d3a3c0..0911acbb131 100644 --- a/app/assets/javascripts/pipelines/components/nav_controls.vue +++ b/app/assets/javascripts/pipelines/components/nav_controls.vue @@ -1,10 +1,13 @@ <script> +import { GlLink, GlButton } from '@gitlab-org/gitlab-ui'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; export default { name: 'PipelineNavControls', components: { LoadingButton, + GlLink, + GlButton, }, props: { newPipelinePath: { @@ -40,28 +43,29 @@ export default { </script> <template> <div class="nav-controls"> - <a + <gl-button v-if="newPipelinePath" :href="newPipelinePath" - class="btn btn-success js-run-pipeline" + variant="success" + class="js-run-pipeline" > {{ s__('Pipelines|Run Pipeline') }} - </a> + </gl-button> <loading-button v-if="resetCachePath" :loading="isResetCacheButtonLoading" :label="s__('Pipelines|Clear Runner Caches')" - class="btn btn-default js-clear-cache" + class="js-clear-cache" @click="onClickResetCache" /> - <a + <gl-button v-if="ciLintPath" :href="ciLintPath" - class="btn btn-default js-ci-lint" + class="js-ci-lint" > {{ s__('Pipelines|CI Lint') }} - </a> + </gl-button> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 40df07650c9..be4b37f3c8c 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -1,14 +1,15 @@ <script> -import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; -import popover from '../../vue_shared/directives/popover'; +import { GlLink, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import popover from '~/vue_shared/directives/popover'; export default { components: { - userAvatarLink, + UserAvatarLink, + GlLink, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, popover, }, props: { @@ -47,11 +48,12 @@ export default { </script> <template> <div class="table-section section-15 d-none d-sm-none d-md-block pipeline-tags"> - <a + <gl-link :href="pipeline.path" - class="js-pipeline-url-link"> + class="js-pipeline-url-link" + > <span class="pipeline-id">#{{ pipeline.id }}</span> - </a> + </gl-link> <span>by</span> <user-avatar-link v-if="user" @@ -68,36 +70,41 @@ export default { <div class="label-container"> <span v-if="pipeline.flags.latest" - v-tooltip + v-gl-tooltip class="js-pipeline-url-latest badge badge-success" - title="Latest pipeline for this branch"> + title="Latest pipeline for this branch" + > latest </span> <span v-if="pipeline.flags.yaml_errors" - v-tooltip + v-gl-tooltip :title="pipeline.yaml_errors" - class="js-pipeline-url-yaml badge badge-danger"> + class="js-pipeline-url-yaml badge badge-danger" + > yaml invalid </span> <span v-if="pipeline.flags.failure_reason" - v-tooltip + v-gl-tooltip :title="pipeline.failure_reason" - class="js-pipeline-url-failure badge badge-danger"> + class="js-pipeline-url-failure badge badge-danger" + > error </span> - <a + <gl-link v-if="pipeline.flags.auto_devops" v-popover="popoverOptions" tabindex="0" class="js-pipeline-url-autodevops badge badge-info autodevops-badge" - role="button"> + role="button" + > Auto DevOps - </a> + </gl-link> <span v-if="pipeline.flags.stuck" - class="js-pipeline-url-stuck badge badge-warning"> + class="js-pipeline-url-stuck badge badge-warning" + > stuck </span> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index cb47704ca26..811495c45a9 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,18 +1,18 @@ <script> +import { GlButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { s__, sprintf } from '~/locale'; +import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; import eventHub from '../event_hub'; import Icon from '../../vue_shared/components/icon.vue'; -import tooltip from '../../vue_shared/directives/tooltip'; -import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; -import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; export default { directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, components: { Icon, GlCountdown, + GlButton, GlLoadingIcon, }, props: { @@ -59,14 +59,12 @@ export default { </script> <template> <div class="btn-group"> - <button - v-tooltip + <gl-button + v-gl-tooltip :disabled="isLoading" - type="button" class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions" title="Manual job" data-toggle="dropdown" - data-placement="top" aria-label="Manual job" > <icon @@ -78,17 +76,16 @@ export default { aria-hidden="true"> </i> <gl-loading-icon v-if="isLoading" /> - </button> + </gl-button> <ul class="dropdown-menu dropdown-menu-right"> <li v-for="action in actions" :key="action.path" > - <button + <gl-button :class="{ disabled: isActionDisabled(action) }" :disabled="isActionDisabled(action)" - type="button" class="js-pipeline-action-link no-btn btn" @click="onClickAction(action)" > @@ -100,7 +97,7 @@ export default { <icon name="clock" /> <gl-countdown :end-date-string="action.scheduled_at" /> </span> - </button> + </gl-button> </li> </ul> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue index e0f0434e03d..2abb24b87b6 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue @@ -1,13 +1,15 @@ <script> -import tooltip from '../../vue_shared/directives/tooltip'; -import Icon from '../../vue_shared/components/icon.vue'; +import { GlLink, GlButton, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; +import Icon from '~/vue_shared/components/icon.vue'; export default { directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, components: { Icon, + GlLink, + GlButton, }, props: { artifacts: { @@ -22,11 +24,10 @@ export default { class="btn-group" role="group" > - <button - v-tooltip - class="dropdown-toggle btn btn-default build-artifacts js-pipeline-dropdown-download" + <gl-button + v-gl-tooltip + class="dropdown-toggle build-artifacts js-pipeline-dropdown-download" title="Artifacts" - data-placement="top" data-toggle="dropdown" aria-label="Artifacts" > @@ -36,18 +37,19 @@ export default { aria-hidden="true" > </i> - </button> + </gl-button> <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(artifact, i) in artifacts" - :key="i"> - <a + :key="i" + > + <gl-link :href="artifact.path" rel="nofollow" download > Download {{ artifact.name }} artifacts - </a> + </gl-link> </li> </ul> </div> diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue index 945a33d9622..6b90a1f540e 100644 --- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue +++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue @@ -12,20 +12,18 @@ * css-class="btn-transparent" * /> */ -import tooltip from '../directives/tooltip'; +import { GlButton, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; import Icon from '../components/icon.vue'; export default { name: 'ClipboardButton', - directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, - components: { + GlButton, Icon, }, - props: { text: { type: String, @@ -68,16 +66,12 @@ export default { </script> <template> - <button - v-tooltip + <gl-button + v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }" :class="cssClass" :title="title" :data-clipboard-text="clipboardText" - :data-container="tooltipContainer" - :data-placement="tooltipPlacement" - type="button" - class="btn" > <icon name="duplicate" /> - </button> + </gl-button> </template> diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 71a3fd544f2..ad12cd101b6 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -25,6 +25,12 @@ .cluster-application-row { border-bottom: 1px solid $border-color; padding: $gl-padding; + + &:last-child { + border-bottom: 0; + border-bottom-left-radius: calc(#{$border-radius-default} - 1px); + border-bottom-right-radius: calc(#{$border-radius-default} - 1px); + } } } @@ -73,6 +79,10 @@ padding: $gl-padding-top $gl-padding; } + .card { + margin-bottom: $gl-vert-padding; + } + .empty-state .svg-content img { width: 145px; } @@ -80,6 +90,31 @@ .top-area .nav-controls > .btn.btn-add-cluster { margin-right: 0; } + + .clusters-table { + background-color: $gray-light; + padding: $gl-padding-8; + } + + .badge-light { + background-color: $white-normal; + } + + .gl-responsive-table-row { + padding: $gl-padding; + border: 0; + + &.table-row-header { + background-color: none; + border: 0; + font-weight: bold; + color: $gl-gray-500; + } + } +} + +.cluster-warning { + @include alert-variant(theme-color-level('warning', $alert-bg-level), theme-color-level('warning', $alert-border-level), theme-color-level('warning', $alert-color-level)); } .gcp-signup-offer { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index dcb1275d182..c57c1eee350 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -595,7 +595,6 @@ $note-form-margin-left: 70px; .discussion-actions { float: right; - margin-left: 10px; color: $gray-darkest; @include media-breakpoint-down(xs) { diff --git a/app/assets/stylesheets/pages/pages.scss b/app/assets/stylesheets/pages/pages.scss index fb42dee66d2..374227fe16a 100644 --- a/app/assets/stylesheets/pages/pages.scss +++ b/app/assets/stylesheets/pages/pages.scss @@ -1,7 +1,5 @@ .pages-domain-list { &-item { - position: relative; - display: flex; align-items: center; .domain-status { @@ -44,8 +42,9 @@ } :first-child { - border-bottom-left-radius: $border-radius-default; - border-top-left-radius: $border-radius-default; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + line-height: $gl-line-height; } :not(:first-child) { diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 0dd7500623d..7f874687212 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -100,12 +100,18 @@ module Boards .merge(board_id: params[:board_id], list_id: params[:list_id], request: request) end - def serializer - IssueSerializer.new(current_user: current_user) - end - def serialize_as_json(resource) - serializer.represent(resource, serializer: 'board', include_full_project_path: board.group_board?) + resource.as_json( + only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight], + labels: true, + issue_endpoints: true, + include_full_project_path: board.group_board?, + include: { + project: { only: [:id, :path] }, + assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, + milestone: { only: [:id, :title] } + } + ) end def whitelist_query_limiting diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index f6f2060ebb5..2e9c77ae55c 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -183,13 +183,13 @@ class Clusters::ClustersController < Clusters::BaseController def gcp_cluster @gcp_cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_provider_gcp - end + end.present(current_user: current_user) end def user_cluster @user_cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_platform_kubernetes - end + end.present(current_user: current_user) end def validate_gcp_token diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb index 0bb7b7efed0..515a9eede8e 100644 --- a/app/controllers/concerns/send_file_upload.rb +++ b/app/controllers/concerns/send_file_upload.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SendFileUpload - def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment') + def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment') if attachment # Response-Content-Type will not override an existing Content-Type in # Google Cloud Storage, so the metadata needs to be cleared on GCS for @@ -17,7 +17,7 @@ module SendFileUpload if file_upload.file_storage? send_file file_upload.path, send_params - elsif file_upload.class.proxy_download_enabled? + elsif file_upload.class.proxy_download_enabled? || proxy headers.store(*Gitlab::Workhorse.send_url(file_upload.url(**redirect_params))) head :ok else diff --git a/app/controllers/groups/clusters/applications_controller.rb b/app/controllers/groups/clusters/applications_controller.rb new file mode 100644 index 00000000000..8dd8a01cf40 --- /dev/null +++ b/app/controllers/groups/clusters/applications_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class Groups::Clusters::ApplicationsController < Clusters::ApplicationsController + include ControllerWithCrossProjectAccessCheck + + prepend_before_action :group + requires_cross_project_access + + private + + def clusterable + @clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user) + end + + def group + @group ||= find_routable!(Group, params[:group_id] || params[:id]) + end +end diff --git a/app/controllers/groups/clusters_controller.rb b/app/controllers/groups/clusters_controller.rb new file mode 100644 index 00000000000..50c44b7a58b --- /dev/null +++ b/app/controllers/groups/clusters_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Groups::ClustersController < Clusters::ClustersController + include ControllerWithCrossProjectAccessCheck + + prepend_before_action :check_group_clusters_feature_flag! + prepend_before_action :group + requires_cross_project_access + + layout 'group' + + private + + def clusterable + @clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user) + end + + def group + @group ||= find_routable!(Group, params[:group_id] || params[:id]) + end + + def check_group_clusters_feature_flag! + render_404 unless Feature.enabled?(:group_clusters) + end +end diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb index 312e256ea6c..ae9c17802b9 100644 --- a/app/controllers/projects/artifacts_controller.rb +++ b/app/controllers/projects/artifacts_controller.rb @@ -16,7 +16,7 @@ class Projects::ArtifactsController < Projects::ApplicationController def download return render_404 unless artifacts_file - send_upload(artifacts_file, attachment: artifacts_file.filename) + send_upload(artifacts_file, attachment: artifacts_file.filename, proxy: params[:proxy]) end def browse diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0c313e9e6d3..e9b9b9b7721 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -140,6 +140,10 @@ module GroupsHelper can?(current_user, "read_group_#{resource}".to_sym, @group) end + if can?(current_user, :read_cluster, @group) && Feature.enabled?(:group_clusters) + links << :kubernetes + end + if can?(current_user, :admin_group, @group) links << :settings end diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 8adb99fcb04..a79a97576d1 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -44,7 +44,7 @@ module Clusters private def install_script - ["/usr/bin/kubectl apply -f #{ISTIO_CRDS} >/dev/null"] + ["/usr/bin/kubectl apply -f #{ISTIO_CRDS}"] end end end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 48d6c0daa0f..0ba056e57d4 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -29,7 +29,7 @@ module Clusters # we force autosave to happen when we save `Cluster` model has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true - has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', autosave: true + has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true has_one :application_helm, class_name: 'Clusters::Applications::Helm' has_one :application_ingress, class_name: 'Clusters::Applications::Ingress' @@ -144,6 +144,10 @@ module Clusters ) end + def allow_user_defined_namespace? + project_type? + end + private def restrict_modification diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index d69038be532..ea02ae6c9d8 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -38,6 +38,8 @@ module Clusters validates :namespace, exclusion: { in: RESERVED_NAMESPACES } + validate :no_namespace, unless: :allow_user_defined_namespace? + # We expect to be `active?` only when enabled and cluster is created (the api_url is assigned) validates :api_url, url: true, presence: true validates :token, presence: true @@ -52,6 +54,7 @@ module Clusters delegate :project, to: :cluster, allow_nil: true delegate :enabled?, to: :cluster, allow_nil: true delegate :managed?, to: :cluster, allow_nil: true + delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true delegate :kubernetes_namespace, to: :cluster alias_method :active?, :enabled? @@ -150,7 +153,8 @@ module Clusters end def build_kube_client! - raise "Incomplete settings" unless api_url && actual_namespace + raise "Incomplete settings" unless api_url + raise "No namespace" if cluster.project_type? && actual_namespace.empty? # can probably remove this line once we remove #actual_namespace unless (username && password) || token raise "Either username/password or token is required to access API" @@ -207,6 +211,12 @@ module Clusters self.token = self.token&.strip end + def no_namespace + if namespace + errors.add(:namespace, 'only allowed for project cluster') + end + end + def prevent_modification return unless managed? diff --git a/app/models/issue.rb b/app/models/issue.rb index abdb3448d4e..0de5e434b02 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -231,6 +231,20 @@ class Issue < ActiveRecord::Base def as_json(options = {}) super(options).tap do |json| + if options.key?(:issue_endpoints) && project + url_helper = Gitlab::Routing.url_helpers + + issue_reference = options[:include_full_project_path] ? to_reference(full: true) : to_reference + + json.merge!( + 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), + assignable_labels_endpoint: url_helper.project_labels_path(project, format: :json, include_ancestor_groups: true) + ) + end + if options.key?(:labels) json[:labels] = labels.as_json( project: project, diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index ae7fb5f962a..7769c3d71c0 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -151,12 +151,6 @@ class WikiPage last_version&.sha end - # Returns the Date that this latest version was - # created on. - def created_at - @page.version.date - end - # Returns boolean True or False if this instance # is an old version of the page. def historical? diff --git a/app/policies/clusters/cluster_policy.rb b/app/policies/clusters/cluster_policy.rb index 147943a3d6c..d6d590687e2 100644 --- a/app/policies/clusters/cluster_policy.rb +++ b/app/policies/clusters/cluster_policy.rb @@ -4,11 +4,7 @@ module Clusters class ClusterPolicy < BasePolicy alias_method :cluster, :subject + delegate { cluster.group } delegate { cluster.project } - - rule { can?(:maintainer_access) }.policy do - enable :update_cluster - enable :admin_cluster - end end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 73c93b22c95..6b4e56ef5e4 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -65,6 +65,10 @@ class GroupPolicy < BasePolicy enable :create_projects enable :admin_pipeline enable :admin_build + enable :read_cluster + enable :create_cluster + enable :update_cluster + enable :admin_cluster end rule { owner }.policy do diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a76a083bceb..1c082945299 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -258,6 +258,8 @@ class ProjectPolicy < BasePolicy enable :update_pages enable :read_cluster enable :create_cluster + enable :update_cluster + enable :admin_cluster enable :create_environment_terminal end diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index cff0e74d6ea..9cc137aa3bd 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -43,4 +43,16 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated def cluster_path(cluster, params = {}) raise NotImplementedError end + + def empty_state_help_text + nil + end + + def sidebar_text + raise NotImplementedError + end + + def learn_more_link + raise NotImplementedError + end end diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb index 78d632eb77c..7e6eccb648c 100644 --- a/app/presenters/clusters/cluster_presenter.rb +++ b/app/presenters/clusters/cluster_presenter.rb @@ -15,6 +15,8 @@ module Clusters def show_path if cluster.project_type? project_cluster_path(project, cluster) + elsif cluster.group_type? + group_cluster_path(group, cluster) else raise NotImplementedError end diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb new file mode 100644 index 00000000000..d963c188559 --- /dev/null +++ b/app/presenters/group_clusterable_presenter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class GroupClusterablePresenter < ClusterablePresenter + extend ::Gitlab::Utils::Override + include ActionView::Helpers::UrlHelper + + override :cluster_status_cluster_path + def cluster_status_cluster_path(cluster, params = {}) + cluster_status_group_cluster_path(clusterable, cluster, params) + end + + override :install_applications_cluster_path + def install_applications_cluster_path(cluster, application) + install_applications_group_cluster_path(clusterable, cluster, application) + end + + override :cluster_path + def cluster_path(cluster, params = {}) + group_cluster_path(clusterable, cluster, params) + end + + override :empty_state_help_text + def empty_state_help_text + s_('ClusterIntegration|Adding an integration to your group will share the cluster across all your projects.') + end + + override :sidebar_text + def sidebar_text + s_('ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster.') + end + + override :learn_more_link + def learn_more_link + link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + end +end diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb index 12077b2e735..63e69b91b11 100644 --- a/app/presenters/project_clusterable_presenter.rb +++ b/app/presenters/project_clusterable_presenter.rb @@ -1,15 +1,31 @@ # frozen_string_literal: true class ProjectClusterablePresenter < ClusterablePresenter + extend ::Gitlab::Utils::Override + include ActionView::Helpers::UrlHelper + + override :cluster_status_cluster_path def cluster_status_cluster_path(cluster, params = {}) cluster_status_project_cluster_path(clusterable, cluster, params) end + override :install_applications_cluster_path def install_applications_cluster_path(cluster, application) install_applications_project_cluster_path(clusterable, cluster, application) end + override :cluster_path def cluster_path(cluster, params = {}) project_cluster_path(clusterable, cluster, params) end + + override :sidebar_text + def sidebar_text + s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + end + + override :learn_more_link + def learn_more_link + link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') + end end diff --git a/app/serializers/README.md b/app/serializers/README.md index bb94745b0b5..0337f88db5f 100644 --- a/app/serializers/README.md +++ b/app/serializers/README.md @@ -180,7 +180,7 @@ def index render json: MyResourceSerializer .new(current_user: @current_user) .represent_details(@project.resources) - end + nd end ``` @@ -196,7 +196,7 @@ def index .represent_details(@project.resources), count: @project.resources.count } - end + nd end ``` diff --git a/app/serializers/issue_board_entity.rb b/app/serializers/issue_board_entity.rb deleted file mode 100644 index 4e3d03b236b..00000000000 --- a/app/serializers/issue_board_entity.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -class IssueBoardEntity < Grape::Entity - include RequestAwareEntity - - expose :id - expose :iid - expose :title - - expose :confidential - expose :due_date - expose :project_id - expose :relative_position - expose :weight, if: -> (*) { respond_to?(:weight) } - expose :time_estimate - - expose :project do |issue| - API::Entities::Project.represent issue.project, only: [:id, :path] - end - - expose :milestone, expose_nil: false do |issue| - API::Entities::Project.represent issue.milestone, only: [:id, :title] - end - - expose :assignees do |issue| - API::Entities::UserBasic.represent issue.assignees, only: [:id, :name, :username, :avatar_url] - end - - expose :labels do |issue| - LabelEntity.represent issue.labels, project: issue.project, only: [:id, :title, :description, :color, :priority, :text_color] - end - - expose :reference_path, if: -> (issue) { issue.project } do |issue, options| - options[:include_full_project_path] ? issue.to_reference(full: true) : issue.to_reference - end - - expose :real_path, if: -> (issue) { issue.project } do |issue| - project_issue_path(issue.project, issue) - end - - expose :issue_sidebar_endpoint, if: -> (issue) { issue.project } do |issue| - project_issue_path(issue.project, issue, format: :json, serializer: 'sidebar') - end - - expose :toggle_subscription_endpoint, if: -> (issue) { issue.project } do |issue| - toggle_subscription_project_issue_path(issue.project, issue) - end - - expose :assignable_labels_endpoint, if: -> (issue) { issue.project } do |issue| - project_labels_path(issue.project, format: :json, include_ancestor_groups: true) - end -end diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb index d66f0a5acb7..37cf5e28396 100644 --- a/app/serializers/issue_serializer.rb +++ b/app/serializers/issue_serializer.rb @@ -4,17 +4,15 @@ class IssueSerializer < BaseSerializer # This overrided method takes care of which entity should be used # to serialize the `issue` based on `basic` key in `opts` param. # Hence, `entity` doesn't need to be declared on the class scope. - def represent(issue, opts = {}) + def represent(merge_request, opts = {}) entity = case opts[:serializer] when 'sidebar' IssueSidebarEntity - when 'board' - IssueBoardEntity else IssueEntity end - super(issue, opts, entity) + super(merge_request, opts, entity) end end diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb index 5082245dda9..98743d62b50 100644 --- a/app/serializers/label_entity.rb +++ b/app/serializers/label_entity.rb @@ -12,8 +12,4 @@ class LabelEntity < Grape::Entity expose :text_color expose :created_at expose :updated_at - - expose :priority, if: -> (*) { options.key?(:project) } do |label| - label.priority(options[:project]) - end end diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb index c348cad4803..844807c2581 100644 --- a/app/services/clusters/applications/create_service.rb +++ b/app/services/clusters/applications/create_service.rb @@ -42,7 +42,16 @@ module Clusters def builders { "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm }, - "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }, + "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress } + }.tap do |hash| + hash.merge!(project_builders) if cluster.project_type? + end + end + + # These applications will need extra configuration to enable them to work + # with groups of projects + def project_builders + { "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus }, "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner }, "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }, diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 270db4a52fd..5a9da053780 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -36,6 +36,10 @@ module Clusters case clusterable when ::Project { cluster_type: :project_type, projects: [clusterable] } + when ::Group + { cluster_type: :group_type, groups: [clusterable] } + else + raise NotImplementedError 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 index a888fab2789..2b607681082 100644 --- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb +++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb @@ -16,8 +16,6 @@ module Clusters configure_kubernetes_token kubernetes_namespace.save! - rescue ::Kubeclient::HttpError => err - raise err unless err.error_code = 404 end private diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 2fbd442fc2e..fbf71f02837 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -24,8 +24,12 @@ module Commits start_project: @start_project, start_branch_name: @start_branch) rescue Gitlab::Git::Repository::CreateTreeError - error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. - This #{@commit.change_type_title(current_user)} may already have been #{action.to_s.dasherize}ed, or a more recent commit may have updated some of its content." + act = action.to_s.dasherize + type = @commit.change_type_title(current_user) + + error_msg = "Sorry, we cannot #{act} this #{type} automatically. " \ + "This #{type} may already have been #{act}ed, or a more recent " \ + "commit may have updated some of its content." raise ChangeError, error_msg end end diff --git a/app/views/clusters/clusters/_buttons.html.haml b/app/views/clusters/clusters/_buttons.html.haml new file mode 100644 index 00000000000..db2e247e341 --- /dev/null +++ b/app/views/clusters/clusters/_buttons.html.haml @@ -0,0 +1,4 @@ +-# This partial is overridden in EE +.nav-controls + %span.btn.btn-add-cluster.disabled.js-add-cluster + = s_("ClusterIntegration|Add Kubernetes cluster") diff --git a/app/views/clusters/clusters/_cluster.html.haml b/app/views/clusters/clusters/_cluster.html.haml index facbcb7fc59..adeca013749 100644 --- a/app/views/clusters/clusters/_cluster.html.haml +++ b/app/views/clusters/clusters/_cluster.html.haml @@ -1,24 +1,16 @@ -.gl-responsive-table-row - .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") - .table-mobile-content - = 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 - .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace") - .table-mobile-content= cluster.platform_kubernetes&.actual_namespace - .table-section.section-10 - .table-mobile-header{ role: "rowheader" } - .table-mobile-content - %button.js-project-feature-toggle.project-feature-toggle{ type: "button", - 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: 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 - = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') - = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') +.card + .card-body.gl-responsive-table-row + .table-section.section-60 + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Kubernetes cluster") + .table-mobile-content + = link_to cluster.name, cluster.show_path + - unless cluster.enabled? + %span.badge.badge-danger Connection disabled + .table-section.section-25 + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope") + .table-mobile-content= cluster.environment_scope + .table-section.section-15.text-right + .table-mobile-header{ role: "rowheader" } + .table-mobile-content + %span.badge.badge-light + = cluster.project_type? ? s_("ClusterIntegration|Project cluster") : s_("ClusterIntegration|Group cluster") diff --git a/app/views/clusters/clusters/_empty_state.html.haml b/app/views/clusters/clusters/_empty_state.html.haml index 800e76d92ef..c926ec258f0 100644 --- a/app/views/clusters/clusters/_empty_state.html.haml +++ b/app/views/clusters/clusters/_empty_state.html.haml @@ -4,8 +4,10 @@ .col-12 .text-content %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation') - - 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} + %p + = s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + = clusterable.empty_state_help_text + = clusterable.learn_more_link - if clusterable.can_create_cluster? .text-center diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml index 3d10348212f..6e4415c21a9 100644 --- a/app/views/clusters/clusters/_sidebar.html.haml +++ b/app/views/clusters/clusters/_sidebar.html.haml @@ -1,9 +1,6 @@ -- clusters_help_url = help_page_path('user/project/clusters/index.md') -- help_link_start = "<a href=\"%{url}\" target=\"_blank\" rel=\"noopener noreferrer\">".html_safe -- help_link_end = '</a>'.html_safe %h4.prepend-top-0 - = s_('ClusterIntegration|Kubernetes cluster integration') + = s_('ClusterIntegration|Add a Kubernetes cluster integration') %p - = s_('ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way.') + = clusterable.sidebar_text %p - = s_('ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: clusters_help_url }, help_link_end: help_link_end } + = clusterable.learn_more_link diff --git a/app/views/clusters/clusters/gcp/_show.html.haml b/app/views/clusters/clusters/gcp/_show.html.haml index ca55ccb8fdf..e9f05eaf453 100644 --- a/app/views/clusters/clusters/gcp/_show.html.haml +++ b/app/views/clusters/clusters/gcp/_show.html.haml @@ -33,9 +33,10 @@ = s_('ClusterIntegration|Show') = clipboard_button(text: @cluster.platform_kubernetes.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default') - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/clusters/clusters/index.html.haml b/app/views/clusters/clusters/index.html.haml index a55de84b5cd..ad6d1d856d6 100644 --- a/app/views/clusters/clusters/index.html.haml +++ b/app/views/clusters/clusters/index.html.haml @@ -10,14 +10,13 @@ .top-area.adjust .nav-text = s_("ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project") - .ci-table.js-clusters-list + = render 'clusters/clusters/buttons' + .clusters-table.js-clusters-list .gl-responsive-table-row.table-row-header{ role: "row" } - .table-section.section-30{ role: "rowheader" } + .table-section.section-60{ role: "rowheader" } = s_("ClusterIntegration|Kubernetes cluster") .table-section.section-30{ role: "rowheader" } = s_("ClusterIntegration|Environment scope") - .table-section.section-30{ role: "rowheader" } - = s_("ClusterIntegration|Project namespace") .table-section.section-10{ role: "rowheader" } - @clusters.each do |cluster| = render "cluster", cluster: cluster.present(current_user: current_user) diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml index 7ea85fe43d6..8a7f7a5c978 100644 --- a/app/views/clusters/clusters/show.html.haml +++ b/app/views/clusters/clusters/show.html.haml @@ -15,6 +15,7 @@ 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_type: @cluster.cluster_type, cluster_status: @cluster.status_name, cluster_status_reason: @cluster.status_reason, help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'), diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index e4758938059..9793c77fc2b 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -21,9 +21,10 @@ = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token'), class: 'label-bold' = platform_kubernetes_field.text_field :token, class: 'form-control', placeholder: s_('ClusterIntegration|Service token'), autocomplete: 'off' - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @user_cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/clusters/clusters/user/_show.html.haml b/app/views/clusters/clusters/user/_show.html.haml index ad8c35e32e3..cac8e72edd3 100644 --- a/app/views/clusters/clusters/user/_show.html.haml +++ b/app/views/clusters/clusters/user/_show.html.haml @@ -22,9 +22,10 @@ %button.js-show-cluster-token.btn-blank{ type: 'button' } = s_('ClusterIntegration|Show') - .form-group - = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' - = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') + - if @cluster.allow_user_defined_namespace? + .form-group + = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' + = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') .form-group .form-check diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index b390c396a09..3cd5168c1f7 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -116,6 +116,19 @@ %strong.fly-out-top-item-name = _('Members') + - if group_sidebar_link?(:kubernetes) + = nav_link(controller: [:clusters]) do + = link_to group_clusters_path(@group) do + .nav-icon-container + = sprite_icon('cloud-gear') + %span.nav-item-name + = _('Kubernetes') + %ul.sidebar-sub-level-items.is-fly-out-only + = nav_link(controller: [:clusters], html_options: { class: "fly-out-top-item" } ) do + = link_to group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do + %strong.fly-out-top-item-name + = _('Kubernetes') + - if group_sidebar_link?(:settings) = nav_link(path: group_nav_link_paths) do = link_to edit_group_path(@group) do diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml index 57345edb90b..ce3ef29c32e 100644 --- a/app/views/projects/pages/_https_only.html.haml +++ b/app/views/projects/pages/_https_only.html.haml @@ -1,9 +1,9 @@ = form_for @project, url: namespace_project_pages_path(@project.namespace.becomes(Namespace), @project), html: { class: 'inline', title: pages_https_only_title } do |f| - = f.check_box :pages_https_only, class: 'float-left', disabled: pages_https_only_disabled? - - .prepend-left-20 - = f.label :pages_https_only, class: pages_https_only_label_class do - %strong Force domains with SSL certificates to use HTTPS + .form-group + .form-check + = f.check_box :pages_https_only, class: 'form-check-input', disabled: pages_https_only_disabled? + = f.label :pages_https_only, class: pages_https_only_label_class do + %strong Force domains with SSL certificates to use HTTPS - unless pages_https_only_disabled? .prepend-top-10 diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index e7178f9160c..2427b4d7611 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -4,9 +4,9 @@ .card .card-header Domains (#{@domains.count}) - %ul.content-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } + %ul.list-group.list-group-flush.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } - @domains.each do |domain| - %li.pages-domain-list-item.unstyled + %li.pages-domain-list-item.list-group-item.d-flex.justify-content-between - if verification_enabled - tooltip, status = domain.unverified? ? [_('Unverified'), 'failed'] : [_('Verified'), 'success'] .domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip } @@ -16,7 +16,7 @@ = domain.url = icon('external-link') - if domain.subject - %p + %div %span.badge.badge-gray Certificate: #{domain.subject} - if domain.expired? %span.badge.badge-danger Expired @@ -24,6 +24,6 @@ = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped" = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - if verification_enabled && domain.unverified? - %li.warning-row + %li.list-group-item.bs-callout-warning #{domain.domain} is not verified. To learn how to verify ownership, visit your #{link_to 'domain details', project_pages_domain_path(@project, domain)}. diff --git a/app/workers/cluster_platform_configure_worker.rb b/app/workers/cluster_platform_configure_worker.rb index 68e8335a09d..8f3689f0166 100644 --- a/app/workers/cluster_platform_configure_worker.rb +++ b/app/workers/cluster_platform_configure_worker.rb @@ -17,6 +17,6 @@ class ClusterPlatformConfigureWorker end rescue ::Kubeclient::HttpError => err - Rails.logger.error "Failed to create/update Kubernetes Namespace. id: #{kubernetes_namespace.id} message: #{err.message}" + Rails.logger.error "Failed to create/update Kubernetes namespace for cluster_id: #{cluster_id} with error: #{err.message}" end end diff --git a/changelogs/unreleased/34758-group-cluster-controller.yml b/changelogs/unreleased/34758-group-cluster-controller.yml new file mode 100644 index 00000000000..88c4c872714 --- /dev/null +++ b/changelogs/unreleased/34758-group-cluster-controller.yml @@ -0,0 +1,5 @@ +--- +title: Add ability to create group level clusters and install gitlab managed applications +merge_request: 22450 +author: +type: added diff --git a/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml b/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml new file mode 100644 index 00000000000..f543730a57d --- /dev/null +++ b/changelogs/unreleased/48475-gitlab-pages-settings-regressions.yml @@ -0,0 +1,5 @@ +--- +title: Fixing regression issues on pages settings and details +merge_request: 22821 +author: +type: fixed diff --git a/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml b/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml new file mode 100644 index 00000000000..1d761d6299c --- /dev/null +++ b/changelogs/unreleased/51259-ci-cd-gitlab-ui-1.yml @@ -0,0 +1,5 @@ +--- +title: Uses new gitlab-ui components in Jobs and Pipelines components +merge_request: +author: +type: other diff --git a/changelogs/unreleased/fix-error-handling-bugs-in-kubernetes-integration.yml b/changelogs/unreleased/fix-error-handling-bugs-in-kubernetes-integration.yml new file mode 100644 index 00000000000..f2a117fe63f --- /dev/null +++ b/changelogs/unreleased/fix-error-handling-bugs-in-kubernetes-integration.yml @@ -0,0 +1,5 @@ +--- +title: Fix error handling bugs in kubernetes integration +merge_request: 22922 +author: +type: fixed diff --git a/changelogs/unreleased/frozen-string-lib-gitlab-more.yml b/changelogs/unreleased/frozen-string-lib-gitlab-more.yml new file mode 100644 index 00000000000..cfbc4ced635 --- /dev/null +++ b/changelogs/unreleased/frozen-string-lib-gitlab-more.yml @@ -0,0 +1,5 @@ +--- +title: Enable even more frozen string in lib/gitlab/**/*.rb +merge_request: +author: gfyoung +type: performance diff --git a/changelogs/unreleased/rs-cherry-pick-api.yml b/changelogs/unreleased/rs-cherry-pick-api.yml new file mode 100644 index 00000000000..ce844dfc939 --- /dev/null +++ b/changelogs/unreleased/rs-cherry-pick-api.yml @@ -0,0 +1,5 @@ +--- +title: Resolve possible cherry pick API race condition +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/rs-revert-api.yml b/changelogs/unreleased/rs-revert-api.yml new file mode 100644 index 00000000000..c07b2fe624c --- /dev/null +++ b/changelogs/unreleased/rs-revert-api.yml @@ -0,0 +1,5 @@ +--- +title: Add revert to commits API +merge_request: 22919 +author: +type: added diff --git a/config/routes/group.rb b/config/routes/group.rb index 2328b50b760..a0aeebe4b91 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -53,6 +53,8 @@ constraints(::Constraints::GroupUrlConstrainer.new) do resource :avatar, only: [:destroy] + concerns :clusterable + resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do post :resend_invite, on: :member delete :leave, on: :collection diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index c58ced7d520..60ad4bf4e8f 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -60,7 +60,7 @@ installations, this can be set in `gitlab-shell/config.yml`. The hooks are searched and executed in this order: -1. `<project>.git/hooks/` - symlink to `gitlab-shell/hooks` global dir +1. `gitlab-shell/hooks` directory as known to Gitaly 1. `<project>.git/hooks/<hook_name>` - executed by `git` itself, this is `gitlab-shell/hooks/<hook_name>` 1. `<project>.git/custom_hooks/<hook_name>` - per project hook (this is already existing behavior) 1. `<project>.git/custom_hooks/<hook_name>.d/*` - per project hooks diff --git a/doc/api/commits.md b/doc/api/commits.md index 9b7ca4b6e70..994eefa423f 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -288,6 +288,47 @@ Example response: } ``` +## Revert a commit + +> [Introduced][ce-22919] in GitLab 11.6. + +Reverts a commit in a given branch. + +``` +POST /projects/:id/repository/commits/:sha/revert +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `sha` | string | yes | Commit SHA to revert | +| `branch` | string | yes | Target branch name | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v4/projects/5/repository/commits/a738f717824ff53aebad8b090c1b79a14f2bd9e8/revert" +``` + +Example response: + +```json +{ + "id":"8b090c1b79a14f2bd9e8a738f717824ff53aebad", + "short_id": "8b090c1b", + "title":"Revert \"Feature added\"", + "created_at":"2018-11-08T15:55:26.000Z", + "parent_ids":["a738f717824ff53aebad8b090c1b79a14f2bd9e8"], + "message":"Revert \"Feature added\"\n\nThis reverts commit a738f717824ff53aebad8b090c1b79a14f2bd9e8", + "author_name":"Administrator", + "author_email":"admin@example.com", + "authored_date":"2018-11-08T15:55:26.000Z", + "committer_name":"Administrator", + "committer_email":"admin@example.com", + "committed_date":"2018-11-08T15:55:26.000Z" +} +``` + ## Get the diff of a commit Get the diff of a commit in a project. @@ -619,3 +660,4 @@ Example response: [ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 [ce-15026]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15026 [ce-18004]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18004 +[ce-22919]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22919 diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_secret_variables.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_secret_variables.png Binary files differdeleted file mode 100644 index 5b5d91ec07a..00000000000 --- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_secret_variables.png +++ /dev/null diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png Binary files differnew file mode 100644 index 00000000000..28323e2d8de --- /dev/null +++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/img/cloud_foundry_variables.png diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md index b88761be56b..3ea81be1569 100644 --- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md +++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md @@ -106,10 +106,10 @@ Now, since the steps defined in `.gitlab-ci.yml` require credentials to login to CF, you'll need to add your CF credentials as [environment variables](../../variables/README.md#predefined-variables-environment-variables) on GitLab CI/CD. To set the environment variables, navigate to your project's -**Settings > CI/CD** and expand **Secret Variables**. Name the variables +**Settings > CI/CD** and expand **Variables**. Name the variables `CF_USERNAME` and `CF_PASSWORD` and set them to the correct values. -![Secret Variable Settings in GitLab](img/cloud_foundry_secret_variables.png) +![Variable Settings in GitLab](img/cloud_foundry_variables.png) Once set up, GitLab CI/CD will deploy your app to CF at every push to your repository's deafult branch. To see the build logs or watch your builds running diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md index f53f7c50281..46effb76d71 100644 --- a/doc/ci/examples/deployment/README.md +++ b/doc/ci/examples/deployment/README.md @@ -101,12 +101,12 @@ production: We created two deploy jobs that are executed on different events: 1. `staging` is executed for all commits that were pushed to `master` branch, -2. `production` is executed for all pushed tags. +1. `production` is executed for all pushed tags. We also use two secure variables: 1. `HEROKU_STAGING_API_KEY` - Heroku API key used to deploy staging app, -2. `HEROKU_PRODUCTION_API_KEY` - Heroku API key used to deploy production app. +1. `HEROKU_PRODUCTION_API_KEY` - Heroku API key used to deploy production app. ## Storing API keys @@ -120,7 +120,7 @@ is hidden in the job log. You access added variable by prefixing it's name with `$` (on non-Windows runners) or `%` (for Windows Batch runners): -1. `$SECRET_VARIABLE` - use it for non-Windows runners -2. `%SECRET_VARIABLE%` - use it for Windows Batch runners +1. `$VARIABLE` - use it for non-Windows runners +1. `%VARIABLE%` - use it for Windows Batch runners Read more about the [CI variables](../../variables/README.md). diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md index bed379b0254..55ff131efaa 100644 --- a/doc/ci/examples/deployment/composer-npm-deploy.md +++ b/doc/ci/examples/deployment/composer-npm-deploy.md @@ -43,7 +43,7 @@ All these operations will put all files into a `build` folder, which is ready to You have multiple options: rsync, scp, sftp and so on. For now, we will use scp. -To make this work, you need to add a GitLab Secret Variable (accessible on _gitlab.example/your-project-name/variables_). That variable will be called `STAGING_PRIVATE_KEY` and it's the **private** ssh key of your server. +To make this work, you need to add a GitLab CI/CD Variable (accessible on _gitlab.example/your-project-name/variables_). That variable will be called `STAGING_PRIVATE_KEY` and it's the **private** ssh key of your server. ### Security tip diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md index b75ed87bc91..cae051daa56 100644 --- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md @@ -418,7 +418,7 @@ fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/late 1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later ![AWS Access Key Config](img/aws_config_window.png) 1. Go to your GitLab project, click **Settings > CI/CD** on the left sidebar -1. Expand the **Secret Variables** section +1. Expand the **Variables** section ![GitLab Secret Config](img/gitlab_config.png) 1. Add a key named `AWS_KEY_ID` and copy the key id from Step 2 into the **Value** textbox 1. Add a key named `AWS_KEY_SECRET` and copy the key secret from Step 2 into the **Value** textbox diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/secret_variables_page.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/secret_variables_page.png Binary files differdeleted file mode 100644 index b7906d49dcb..00000000000 --- a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/secret_variables_page.png +++ /dev/null diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png Binary files differnew file mode 100644 index 00000000000..80d8eb0f4fc --- /dev/null +++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/img/variables_page.png diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md index 70020d461d9..b6989d229d1 100644 --- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md +++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md @@ -120,12 +120,12 @@ Now, let's add it to your GitLab project as a [variable](../../variables/README. Variables are user-defined variables and are stored out of `.gitlab-ci.yml`, for security purposes. They can be added per project by navigating to the project's **Settings** > **CI/CD**. -![variables page](img/secret_variables_page.png) - To the field **KEY**, add the name `SSH_PRIVATE_KEY`, and to the **VALUE** field, paste the private key you've copied earlier. We'll use this variable in the `.gitlab-ci.yml` later, to easily connect to our remote server as the deployer user without entering its password. -We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../../ssh/README.md#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project). +![variables page](img/variables_page.png) + +We also need to add the public key to **Project** > **Settings** > **Repository** as a [Deploy Key](../../../ssh/README.md#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project). ```bash @@ -135,10 +135,10 @@ We also need to add the public key to **Project** > **Settings** > **Repository* cat ~/.ssh/id_rsa.pub ``` -![deploy keys page](img/deploy_keys_page.png) - To the field **Title**, add any name you want, and paste the public key into the **Key** field. +![deploy keys page](img/deploy_keys_page.png) + Now, let's clone our repository on the server just to make sure the `deployer` user has access to the repository. ```bash diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index 8c3ab7643ba..97b1b890836 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -409,7 +409,7 @@ You can use the following fake tokens as examples. | Personal access token | `n671WNGecHugsdEDPsyo` | | Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | | Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | -| Secret CI variable | `Li8j-mLUVA3eZYjPfd_H` | +| CI/CD variable | `Li8j-mLUVA3eZYjPfd_H` | | Specific Runner token | `yrnZW46BrtBFqM7xDzE7dddd` | | Shared Runner token | `6Vk7ZsosqQyfreAxXTZr` | | Trigger token | `be20d8dcc028677c931e04f3871a9b` | diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 4f4ff85fe1d..36d150c8a5b 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -1,75 +1,82 @@ -# Review apps +# Review Apps -Review Apps are automatically deployed by each pipeline, both in +Review Apps are automatically deployed by each pipeline, both in [CE](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22010) and [EE](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6665). ## How does it work? -1. On every [pipeline][gitlab-pipeline] during the `test` stage, the +1. On every [pipeline][gitlab-pipeline] during the `test` stage, the [`review` job][review-job] is automatically started. 1. The `review` job [triggers a pipeline][cng-pipeline] in the - [`CNG-mirror`][cng-mirror] [^1] project -1. The `CNG-mirror` pipeline creates the Docker images of each component (e.g. `gitlab-rails-ee`, - `gitlab-shell`, `gitaly` etc.) based on the commit from the + [`CNG-mirror`][cng-mirror] project. + - We use the `CNG-mirror` project so that the `CNG`, (**C**loud **N**ative + **G**itLab), project's registry is not overloaded with a lot of transient + Docker images. +1. The `CNG-mirror` pipeline creates the Docker images of each component (e.g. + `gitlab-rails-ee`, `gitlab-shell`, `gitaly` etc.) based on the commit from the [GitLab pipeline][gitlab-pipeline] and store them in its - [registry][cng-mirror-registry] -1. Once all images are built, the review app is deployed using - [the official GitLab Helm chart][helm-chart] [^2] to the + [registry][cng-mirror-registry]. +1. Once all images are built, the Review App is deployed using + [the official GitLab Helm chart][helm-chart] to the [`review-apps-ee` Kubernetes cluster on GCP][review-apps-ee] - - The actual scripts used to deploy the review app can be found at - [`scripts/review_apps/review-apps.sh`][review-apps.sh] - - These scripts are basically - [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the - default CNG images are overriden with the images built and stored in the - [`CNG-mirror` project's registry][cng-mirror-registry] -1. Once the `review` job succeeds, you should be able to use your review app + - The actual scripts used to deploy the Review App can be found at + [`scripts/review_apps/review-apps.sh`][review-apps.sh] + - These scripts are basically + [our official Auto DevOps scripts][Auto-DevOps.gitlab-ci.yml] where the + default CNG images are overriden with the images built and stored in the + [`CNG-mirror` project's registry][cng-mirror-registry]. + - Since we're using [the official GitLab Helm chart][helm-chart], this means + you get a dedicated environment for your branch that's very close to what it + would look in production. +1. Once the `review` job succeeds, you should be able to use your Review App thanks to the direct link to it from the MR widget. The default username is `root` and its password can be found in the 1Password secure note named - **gitlab-{ce,ee} review app's root password**. + **gitlab-{ce,ee} Review App's root password** (note that there's currently + [a bug where the default password seems to be overriden][password-bug]). **Additional notes:** -- The Kubernetes cluster is connected to the `gitlab-ee` project using [GitLab's - Kubernetes integration][gitlab-k8s-integration]. This basically allows to have - a link to the review app directly from the merge request widget. -- The manual `stop_review` in the `post-cleanup` stage can be used to stop a - review app manually, and is also started by GitLab once a branch is deleted -- [TBD] Review apps are cleaned up regularly using a pipeline schedule that runs - the [`scripts/review_apps/automated_cleanup.rb`][automated_cleanup.rb] script -- If you're unable to log in using the `root` username and password, the - deployment may have failed. Stop the Review App via the `stop_review` +- The Kubernetes cluster is connected to the `gitlab-{ce,ee}` projects using + [GitLab's Kubernetes integration][gitlab-k8s-integration]. This basically + allows to have a link to the Review App directly from the merge request widget. +- The manual `stop_review` in the `test` stage can be used to stop a Review App + manually, and is also started by GitLab once a branch is deleted. +- Review Apps are cleaned up regularly using a pipeline schedule that runs + the [`scripts/review_apps/automated_cleanup.rb`][automated_cleanup.rb] script. +- If the Review App deployment fails, you can simply retry it (there's no need + to run the `stop_review` job first). +- If you're unable to log in using the `root` username and password, you may + encounter [this bug][password-bug]. Stop the Review App via the `stop_review` manual job and then retry the `review` job to redeploy the Review App. -[^1]: We use the `CNG-mirror` project so that the `CNG`, (**C**loud **N**ative **G**itLab), project's registry is - not overloaded with a lot of transient Docker images. -[^2]: Since we're using [the official GitLab Helm chart][helm-chart], this means - you get the a dedicated environment for your branch that's very close to what it - would look in production - ## Frequently Asked Questions -**Will it be too much to trigger CNG image builds on every test run? This could create thousands of unused docker images.** +**Isn't it too much to trigger CNG image builds on every test run? This creates +thousands of unused Docker images.** - > We have to start somewhere and improve later. If we see this getting out of hand, we will revisit. + > We have to start somewhere and improve later. Also, we're using the + CNG-mirror project to store these Docker images so that we can just wipe out + the registry at some point, and use a new fresh, empty one. -**How big is the Kubernetes cluster?** +**How big are the Kubernetes clusters (`review-apps-ce` and `review-apps-ee`)?** - > The cluster is currently setup with a single pool of preemptible - nodes, with a minimum of 1 node and a maximum of 30 nodes. + > The clusters are currently set up with a single pool of preemptible nodes, + with a minimum of 1 node and a maximum of 100 nodes. **What are the machine running on the cluster?** > We're currently using `n1-standard-4` (4 vCPUs, 15 GB memory) machines. -**How do we secure this from abuse? Apps are open to the world so we need to find a way to limit it to only us.** +**How do we secure this from abuse? Apps are open to the world so we need to +find a way to limit it to only us.** - > This won't work for forks. We will add a root password to 1password shared vault. + > This isn't enabled for forks. -[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ee/pipelines/29302122 -[review-job]: https://gitlab.com/gitlab-org/gitlab-ee/-/jobs/94294136 +[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/35850709 +[review-job]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/118076368 [cng-mirror]: https://gitlab.com/gitlab-org/build/CNG-mirror -[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/29307727 +[cng-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/35883435 [cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry [helm-chart]: https://gitlab.com/charts/gitlab/ [review-apps-ee]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps @@ -77,6 +84,7 @@ Review Apps are automatically deployed by each pipeline, both in [automated_cleanup.rb]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/automated_cleanup.rb [Auto-DevOps.gitlab-ci.yml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml [gitlab-k8s-integration]: https://docs.gitlab.com/ee/user/project/clusters/index.html +[password-bug]: https://gitlab.com/gitlab-org/gitlab-ce/issues/53621 --- diff --git a/doc/install/kubernetes/gitlab_runner_chart.md b/doc/install/kubernetes/gitlab_runner_chart.md index 2aab225fcdb..f34d398a7f5 100644 --- a/doc/install/kubernetes/gitlab_runner_chart.md +++ b/doc/install/kubernetes/gitlab_runner_chart.md @@ -1,6 +1,6 @@ # GitLab Runner Helm Chart > **Note:** -These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/charts/gitlab-runner/issues). +These charts have been tested on Google Kubernetes Engine and Azure Container Service. Other Kubernetes installations may work as well, if not please [open an issue](https://gitlab.com/gitlab-org/gitlab-runner/issues). The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your Kubernetes cluster. @@ -132,7 +132,7 @@ runners: If your cluster has RBAC enabled, you can choose to either have the chart create its own service account or provide one. -To have the chart create the service account for you, set `rbac.create` to true. +To have the chart create the service account for you, set `rbac.create` to true. ### Controlling maximum Runner concurrency diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 96e788666a1..3647f600b21 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -586,7 +586,7 @@ repo or by specifying a project variable: file in it, Auto DevOps will detect the chart and use it instead of the [default one](https://gitlab.com/charts/auto-deploy-app). This can be a great way to control exactly how your application is deployed. -- **Project variable** - Create a [project variable](../../ci/variables/README.md#secret-variables) +- **Project variable** - Create a [project variable](../../ci/variables/README.md#variables) `AUTO_DEVOPS_CHART` with the URL of a custom chart to use. ### Customizing `.gitlab-ci.yml` @@ -660,7 +660,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac TIP: **Tip:** Set up the replica variables using a -[project variable](../../ci/variables/README.md#secret-variables) +[project variable](../../ci/variables/README.md#variables) and scale your application by just redeploying it! CAUTION: **Caution:** @@ -738,7 +738,7 @@ staging environment and deploy to production manually. For this scenario, the `STAGING_ENABLED` environment variable was introduced. If `STAGING_ENABLED` is defined in your project (e.g., set `STAGING_ENABLED` to -`1` as a secret variable), then the application will be automatically deployed +`1` as a CI/CD variable), then the application will be automatically deployed to a `staging` environment, and a `production_manual` job will be created for you when you're ready to manually deploy to production. @@ -751,7 +751,7 @@ A [canary environment](https://docs.gitlab.com/ee/user/project/canary_deployment before any changes are deployed to production. If `CANARY_ENABLED` is defined in your project (e.g., set `CANARY_ENABLED` to -`1` as a secret variable) then two manual jobs will be created: +`1` as a CI/CD variable) then two manual jobs will be created: - `canary` which will deploy the application to the canary environment - `production_manual` which is to be used by you when you're ready to manually diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 233ed205790..3fbd4c21eab 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -145,9 +145,10 @@ service accounts and privileges in order to install and run - A `gitlab` service account with `cluster-admin` privileges will be created in the `default` namespace, which will be used by GitLab to manage the newly created cluster. -- A project service account with `edit` privileges will be created in - the project namespace (also created by GitLab), which will be used in - [deployment jobs](#deployment-variables). +- A project service account with [`edit` + privileges](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) + will be created in the project namespace (also created by GitLab), which will + be used in [deployment jobs](#deployment-variables). NOTE: **Note:** Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716) in GitLab 11.5. diff --git a/lib/api/commits.rb b/lib/api/commits.rb index e59abd3e3d0..3b8f3fedccf 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -194,11 +194,47 @@ module API branch_name: params[:branch] } - result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute + result = ::Commits::CherryPickService + .new(user_project, current_user, commit_params) + .execute if result[:status] == :success - branch = find_branch!(params[:branch]) - present user_project.repository.commit(branch.dereferenced_target), with: Entities::Commit + present user_project.repository.commit(result[:result]), + with: Entities::Commit + else + render_api_error!(result[:message], 400) + end + end + + desc 'Revert a commit in a branch' do + detail 'This feature was introduced in GitLab 11.6' + success Entities::Commit + end + params do + requires :sha, type: String, desc: 'Commit SHA to revert' + requires :branch, type: String, desc: 'Target branch name', allow_blank: false + end + post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + authorize_push_to_branch!(params[:branch]) + + commit = user_project.commit(params[:sha]) + not_found!('Commit') unless commit + + find_branch!(params[:branch]) + + commit_params = { + commit: commit, + start_branch: params[:branch], + branch_name: params[:branch] + } + + result = ::Commits::RevertService + .new(user_project, current_user, commit_params) + .execute + + if result[:status] == :success + present user_project.repository.commit(result[:result]), + with: Entities::Commit else render_api_error!(result[:message], 400) end diff --git a/lib/gitlab/background_migration/redact_links.rb b/lib/gitlab/background_migration/redact_links.rb index f5d3bcdd517..92256e59a6c 100644 --- a/lib/gitlab/background_migration/redact_links.rb +++ b/lib/gitlab/background_migration/redact_links.rb @@ -1,25 +1,14 @@ # frozen_string_literal: true # rubocop:disable Style/Documentation +require_relative 'redact_links/redactable' + module Gitlab module BackgroundMigration class RedactLinks - module Redactable - extend ActiveSupport::Concern - - def redact_field!(field) - self[field].gsub!(%r{/sent_notifications/\h{32}/unsubscribe}, '/sent_notifications/REDACTED/unsubscribe') - - if self.changed? - self.update_columns(field => self[field], - "#{field}_html" => nil) - end - end - end - class Note < ActiveRecord::Base include EachBatch - include Redactable + include ::Gitlab::BackgroundMigration::RedactLinks::Redactable self.table_name = 'notes' self.inheritance_column = :_type_disabled @@ -27,7 +16,7 @@ module Gitlab class Issue < ActiveRecord::Base include EachBatch - include Redactable + include ::Gitlab::BackgroundMigration::RedactLinks::Redactable self.table_name = 'issues' self.inheritance_column = :_type_disabled @@ -35,7 +24,7 @@ module Gitlab class MergeRequest < ActiveRecord::Base include EachBatch - include Redactable + include ::Gitlab::BackgroundMigration::RedactLinks::Redactable self.table_name = 'merge_requests' self.inheritance_column = :_type_disabled @@ -43,7 +32,7 @@ module Gitlab class Snippet < ActiveRecord::Base include EachBatch - include Redactable + include ::Gitlab::BackgroundMigration::RedactLinks::Redactable self.table_name = 'snippets' self.inheritance_column = :_type_disabled diff --git a/lib/gitlab/background_migration/redact_links/redactable.rb b/lib/gitlab/background_migration/redact_links/redactable.rb new file mode 100644 index 00000000000..baab34221f1 --- /dev/null +++ b/lib/gitlab/background_migration/redact_links/redactable.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class RedactLinks + module Redactable + extend ActiveSupport::Concern + + def redact_field!(field) + self[field].gsub!(%r{/sent_notifications/\h{32}/unsubscribe}, '/sent_notifications/REDACTED/unsubscribe') + + if self.changed? + self.update_columns(field => self[field], + "#{field}_html" => nil) + end + end + end + end + end +end diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb index 501c2111530..0ca99506311 100644 --- a/lib/gitlab/conflict/file.rb +++ b/lib/gitlab/conflict/file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Conflict class File diff --git a/lib/gitlab/conflict/file_collection.rb b/lib/gitlab/conflict/file_collection.rb index 65a65b67975..53406af2c4e 100644 --- a/lib/gitlab/conflict/file_collection.rb +++ b/lib/gitlab/conflict/file_collection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Conflict class FileCollection diff --git a/lib/gitlab/cross_project_access/check_collection.rb b/lib/gitlab/cross_project_access/check_collection.rb index 88376232065..55527ba5e87 100644 --- a/lib/gitlab/cross_project_access/check_collection.rb +++ b/lib/gitlab/cross_project_access/check_collection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class CrossProjectAccess class CheckCollection diff --git a/lib/gitlab/cross_project_access/check_info.rb b/lib/gitlab/cross_project_access/check_info.rb index e8a845c7f1e..2a9eacad680 100644 --- a/lib/gitlab/cross_project_access/check_info.rb +++ b/lib/gitlab/cross_project_access/check_info.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class CrossProjectAccess class CheckInfo diff --git a/lib/gitlab/cross_project_access/class_methods.rb b/lib/gitlab/cross_project_access/class_methods.rb index 90eac94800c..64ad30794d3 100644 --- a/lib/gitlab/cross_project_access/class_methods.rb +++ b/lib/gitlab/cross_project_access/class_methods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class CrossProjectAccess module ClassMethods diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb index e3e3767cc75..304d60996a6 100644 --- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index 86d708be0d6..36231b187cd 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module BaseQuery diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb index 038d5a19bc4..e2d6a301734 100644 --- a/lib/gitlab/cycle_analytics/base_stage.rb +++ b/lib/gitlab/cycle_analytics/base_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class BaseStage diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb index 06357c9b377..591db3c35e6 100644 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class CodeEventFetcher < BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb index 5f9dc9a4303..2e5f9ef5a40 100644 --- a/lib/gitlab/cycle_analytics/code_stage.rb +++ b/lib/gitlab/cycle_analytics/code_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class CodeStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/event_fetcher.rb b/lib/gitlab/cycle_analytics/event_fetcher.rb index 50e126cf00b..98a30a8fc97 100644 --- a/lib/gitlab/cycle_analytics/event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module EventFetcher diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb index 1754f91dccb..30c6ead8968 100644 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class IssueEventFetcher < BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb index 7b03811efb2..4eae2da512c 100644 --- a/lib/gitlab/cycle_analytics/issue_stage.rb +++ b/lib/gitlab/cycle_analytics/issue_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class IssueStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb index f5d08c0b658..3e0302d308d 100644 --- a/lib/gitlab/cycle_analytics/metrics_tables.rb +++ b/lib/gitlab/cycle_analytics/metrics_tables.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module MetricsTables diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb index 1e11e84a9cb..afefd09b614 100644 --- a/lib/gitlab/cycle_analytics/permissions.rb +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class Permissions diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index 086203b9ccc..db8ac3becea 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class PlanEventFetcher < BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb index 1a0afb56b4f..513e4575be0 100644 --- a/lib/gitlab/cycle_analytics/plan_stage.rb +++ b/lib/gitlab/cycle_analytics/plan_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class PlanStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb index 0fa2e87f673..6681cb42c90 100644 --- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class ProductionEventFetcher < IssueEventFetcher diff --git a/lib/gitlab/cycle_analytics/production_helper.rb b/lib/gitlab/cycle_analytics/production_helper.rb index d0ca62e46e4..aff65b150fb 100644 --- a/lib/gitlab/cycle_analytics/production_helper.rb +++ b/lib/gitlab/cycle_analytics/production_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module ProductionHelper diff --git a/lib/gitlab/cycle_analytics/production_stage.rb b/lib/gitlab/cycle_analytics/production_stage.rb index 0fa8a65cb99..6fd7214dce7 100644 --- a/lib/gitlab/cycle_analytics/production_stage.rb +++ b/lib/gitlab/cycle_analytics/production_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class ProductionStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb index dada819a2a8..de100295281 100644 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class ReviewEventFetcher < BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb index cfbbdc43fd9..294b656bc55 100644 --- a/lib/gitlab/cycle_analytics/review_stage.rb +++ b/lib/gitlab/cycle_analytics/review_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class ReviewStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/stage.rb b/lib/gitlab/cycle_analytics/stage.rb index 28e0455df59..1bd40a7aa18 100644 --- a/lib/gitlab/cycle_analytics/stage.rb +++ b/lib/gitlab/cycle_analytics/stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module Stage diff --git a/lib/gitlab/cycle_analytics/stage_summary.rb b/lib/gitlab/cycle_analytics/stage_summary.rb index fc77bd86097..5198dd5b4eb 100644 --- a/lib/gitlab/cycle_analytics/stage_summary.rb +++ b/lib/gitlab/cycle_analytics/stage_summary.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class StageSummary diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb index 2f014153ca5..70ce82383b3 100644 --- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class StagingEventFetcher < BaseEventFetcher diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb index d5684bb9201..dbc2414ff66 100644 --- a/lib/gitlab/cycle_analytics/staging_stage.rb +++ b/lib/gitlab/cycle_analytics/staging_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class StagingStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/summary/base.rb b/lib/gitlab/cycle_analytics/summary/base.rb index a917ddccac7..709221c648e 100644 --- a/lib/gitlab/cycle_analytics/summary/base.rb +++ b/lib/gitlab/cycle_analytics/summary/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module Summary diff --git a/lib/gitlab/cycle_analytics/summary/commit.rb b/lib/gitlab/cycle_analytics/summary/commit.rb index 550c1755a71..f0019b26fa2 100644 --- a/lib/gitlab/cycle_analytics/summary/commit.rb +++ b/lib/gitlab/cycle_analytics/summary/commit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module Summary diff --git a/lib/gitlab/cycle_analytics/summary/deploy.rb b/lib/gitlab/cycle_analytics/summary/deploy.rb index 099d798aac6..3b56dc2a7bc 100644 --- a/lib/gitlab/cycle_analytics/summary/deploy.rb +++ b/lib/gitlab/cycle_analytics/summary/deploy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module Summary diff --git a/lib/gitlab/cycle_analytics/summary/issue.rb b/lib/gitlab/cycle_analytics/summary/issue.rb index 9bbf7a2685f..51695c86192 100644 --- a/lib/gitlab/cycle_analytics/summary/issue.rb +++ b/lib/gitlab/cycle_analytics/summary/issue.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics module Summary diff --git a/lib/gitlab/cycle_analytics/test_event_fetcher.rb b/lib/gitlab/cycle_analytics/test_event_fetcher.rb index a2589c6601a..4d5ea5b7c34 100644 --- a/lib/gitlab/cycle_analytics/test_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/test_event_fetcher.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class TestEventFetcher < StagingEventFetcher diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb index 0e9d235ca79..c31b664148b 100644 --- a/lib/gitlab/cycle_analytics/test_stage.rb +++ b/lib/gitlab/cycle_analytics/test_stage.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class TestStage < BaseStage diff --git a/lib/gitlab/cycle_analytics/updater.rb b/lib/gitlab/cycle_analytics/updater.rb index 953268ebd46..c642809a792 100644 --- a/lib/gitlab/cycle_analytics/updater.rb +++ b/lib/gitlab/cycle_analytics/updater.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class Updater diff --git a/lib/gitlab/cycle_analytics/usage_data.rb b/lib/gitlab/cycle_analytics/usage_data.rb index 5122e3417ca..913ee373f54 100644 --- a/lib/gitlab/cycle_analytics/usage_data.rb +++ b/lib/gitlab/cycle_analytics/usage_data.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module CycleAnalytics class UsageData diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb index 0b71b31a476..3407380127e 100644 --- a/lib/gitlab/data_builder/build.rb +++ b/lib/gitlab/data_builder/build.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module Build diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb index f573368e572..65601dcdf31 100644 --- a/lib/gitlab/data_builder/note.rb +++ b/lib/gitlab/data_builder/note.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module Note diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index f382992cb0a..76c8b4ec5c2 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module Pipeline diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb index b498f113859..9bf2f9291a8 100644 --- a/lib/gitlab/data_builder/push.rb +++ b/lib/gitlab/data_builder/push.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module Push diff --git a/lib/gitlab/data_builder/repository.rb b/lib/gitlab/data_builder/repository.rb index c9c13ec6487..0e627fd623e 100644 --- a/lib/gitlab/data_builder/repository.rb +++ b/lib/gitlab/data_builder/repository.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module Repository diff --git a/lib/gitlab/data_builder/wiki_page.rb b/lib/gitlab/data_builder/wiki_page.rb index 226974b698c..9368446fa59 100644 --- a/lib/gitlab/data_builder/wiki_page.rb +++ b/lib/gitlab/data_builder/wiki_page.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DataBuilder module WikiPage diff --git a/lib/gitlab/database/arel_methods.rb b/lib/gitlab/database/arel_methods.rb index d7e3ce08b32..991e4152dcb 100644 --- a/lib/gitlab/database/arel_methods.rb +++ b/lib/gitlab/database/arel_methods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module ArelMethods diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb index 5f549ed2b3c..ea6529e2dc4 100644 --- a/lib/gitlab/database/count.rb +++ b/lib/gitlab/database/count.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # For large tables, PostgreSQL can take a long time to count rows due to MVCC. # We can optimize this by using the reltuples count as described in https://wiki.postgresql.org/wiki/Slow_Counting. module Gitlab diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb index 25e56998038..79d2caff151 100644 --- a/lib/gitlab/database/date_time.rb +++ b/lib/gitlab/database/date_time.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module DateTime diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb index 7d334a79009..862ab96c887 100644 --- a/lib/gitlab/database/grant.rb +++ b/lib/gitlab/database/grant.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database # Model that can be used for querying permissions of a SQL user. diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb index f64e3d53138..0da5119a3ed 100644 --- a/lib/gitlab/database/median.rb +++ b/lib/gitlab/database/median.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # https://www.periscopedata.com/blog/medians-in-sql.html module Gitlab module Database diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index f98d6dbd46f..134d1e7a724 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module MigrationHelpers diff --git a/lib/gitlab/database/multi_threaded_migration.rb b/lib/gitlab/database/multi_threaded_migration.rb index 7ae5a4c17c8..1d39a3d0b57 100644 --- a/lib/gitlab/database/multi_threaded_migration.rb +++ b/lib/gitlab/database/multi_threaded_migration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module MultiThreadedMigration diff --git a/lib/gitlab/database/read_only_relation.rb b/lib/gitlab/database/read_only_relation.rb index 4571ad122ce..2362208e5dd 100644 --- a/lib/gitlab/database/read_only_relation.rb +++ b/lib/gitlab/database/read_only_relation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database # Module that can be injected into a ActiveRecord::Relation to make it diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb index f333ff22300..2314246da55 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This module can be included in migrations to make it easier to rename paths # of `Namespace` & `Project` models certain paths would become `reserved`. # diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb index 26ae6966746..f1dc3ed74fe 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module RenameReservedPathsMigration diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb index 14de28a1d08..a5b42bbfdd9 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module RenameReservedPathsMigration diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb index 73971af6a74..6bbad707f0f 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module RenameReservedPathsMigration diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb index 827aeb12a02..580be9fe267 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database module RenameReservedPathsMigration diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb index b2d8ee81977..6516d6e648d 100644 --- a/lib/gitlab/database/sha_attribute.rb +++ b/lib/gitlab/database/sha_attribute.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Database BINARY_TYPE = diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb index d2360583741..ac2efe598b4 100644 --- a/lib/gitlab/dependency_linker/base_linker.rb +++ b/lib/gitlab/dependency_linker/base_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class BaseLinker diff --git a/lib/gitlab/dependency_linker/cartfile_linker.rb b/lib/gitlab/dependency_linker/cartfile_linker.rb index 4f69f2c4ab2..0e33f0956dd 100644 --- a/lib/gitlab/dependency_linker/cartfile_linker.rb +++ b/lib/gitlab/dependency_linker/cartfile_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class CartfileLinker < MethodLinker diff --git a/lib/gitlab/dependency_linker/cocoapods.rb b/lib/gitlab/dependency_linker/cocoapods.rb index 2fbde7da1b4..38eabe303de 100644 --- a/lib/gitlab/dependency_linker/cocoapods.rb +++ b/lib/gitlab/dependency_linker/cocoapods.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker module Cocoapods diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb index cfd4ec15125..22d2bead891 100644 --- a/lib/gitlab/dependency_linker/composer_json_linker.rb +++ b/lib/gitlab/dependency_linker/composer_json_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class ComposerJsonLinker < PackageJsonLinker diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb index bfea836bcb2..8ab219c4962 100644 --- a/lib/gitlab/dependency_linker/gemfile_linker.rb +++ b/lib/gitlab/dependency_linker/gemfile_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class GemfileLinker < MethodLinker diff --git a/lib/gitlab/dependency_linker/gemspec_linker.rb b/lib/gitlab/dependency_linker/gemspec_linker.rb index f1783ee2ab4..b924ea86d89 100644 --- a/lib/gitlab/dependency_linker/gemspec_linker.rb +++ b/lib/gitlab/dependency_linker/gemspec_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class GemspecLinker < MethodLinker diff --git a/lib/gitlab/dependency_linker/godeps_json_linker.rb b/lib/gitlab/dependency_linker/godeps_json_linker.rb index fe091baee6d..d24c137793e 100644 --- a/lib/gitlab/dependency_linker/godeps_json_linker.rb +++ b/lib/gitlab/dependency_linker/godeps_json_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class GodepsJsonLinker < JsonLinker diff --git a/lib/gitlab/dependency_linker/json_linker.rb b/lib/gitlab/dependency_linker/json_linker.rb index a8ef25233d8..298d214df61 100644 --- a/lib/gitlab/dependency_linker/json_linker.rb +++ b/lib/gitlab/dependency_linker/json_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class JsonLinker < BaseLinker diff --git a/lib/gitlab/dependency_linker/method_linker.rb b/lib/gitlab/dependency_linker/method_linker.rb index 0ffa2a83c93..d4d85bb3390 100644 --- a/lib/gitlab/dependency_linker/method_linker.rb +++ b/lib/gitlab/dependency_linker/method_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class MethodLinker < BaseLinker diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb index 330c95f0880..578e25f806a 100644 --- a/lib/gitlab/dependency_linker/package_json_linker.rb +++ b/lib/gitlab/dependency_linker/package_json_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class PackageJsonLinker < JsonLinker diff --git a/lib/gitlab/dependency_linker/podfile_linker.rb b/lib/gitlab/dependency_linker/podfile_linker.rb index 60ad166ea17..def9b04cca9 100644 --- a/lib/gitlab/dependency_linker/podfile_linker.rb +++ b/lib/gitlab/dependency_linker/podfile_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class PodfileLinker < GemfileLinker diff --git a/lib/gitlab/dependency_linker/podspec_json_linker.rb b/lib/gitlab/dependency_linker/podspec_json_linker.rb index d82237ed3f1..1a2493e7cc0 100644 --- a/lib/gitlab/dependency_linker/podspec_json_linker.rb +++ b/lib/gitlab/dependency_linker/podspec_json_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class PodspecJsonLinker < JsonLinker diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb index 924e55e9820..6b1758c5a43 100644 --- a/lib/gitlab/dependency_linker/podspec_linker.rb +++ b/lib/gitlab/dependency_linker/podspec_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class PodspecLinker < MethodLinker diff --git a/lib/gitlab/dependency_linker/requirements_txt_linker.rb b/lib/gitlab/dependency_linker/requirements_txt_linker.rb index 9c9620bc36a..f630c13b760 100644 --- a/lib/gitlab/dependency_linker/requirements_txt_linker.rb +++ b/lib/gitlab/dependency_linker/requirements_txt_linker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module DependencyLinker class RequirementsTxtLinker < BaseLinker diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb index 81df47964be..d4823f60826 100644 --- a/lib/gitlab/diff/diff_refs.rb +++ b/lib/gitlab/diff/diff_refs.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class DiffRefs diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index fb117baca9e..f3bd8b69869 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class File diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb index 2ad6fe8449d..10df037a0dd 100644 --- a/lib/gitlab/diff/file_collection/base.rb +++ b/lib/gitlab/diff/file_collection/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module FileCollection diff --git a/lib/gitlab/diff/file_collection/commit.rb b/lib/gitlab/diff/file_collection/commit.rb index 4dc297ec036..7b1d6171e82 100644 --- a/lib/gitlab/diff/file_collection/commit.rb +++ b/lib/gitlab/diff/file_collection/commit.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module FileCollection diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb index 20d8f891cc3..586c5cf87af 100644 --- a/lib/gitlab/diff/file_collection/compare.rb +++ b/lib/gitlab/diff/file_collection/compare.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module FileCollection diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index 0dd073a3a8e..e29bf75f341 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module FileCollection diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb index 5e923b9e602..9704aed82c1 100644 --- a/lib/gitlab/diff/formatters/base_formatter.rb +++ b/lib/gitlab/diff/formatters/base_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module Formatters diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb index ccd0d309972..5bc9f0c337f 100644 --- a/lib/gitlab/diff/formatters/image_formatter.rb +++ b/lib/gitlab/diff/formatters/image_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module Formatters diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb index 01c7e9f51ab..f6e247ef665 100644 --- a/lib/gitlab/diff/formatters/text_formatter.rb +++ b/lib/gitlab/diff/formatters/text_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff module Formatters diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 1d833183ec3..d2484217ab9 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class Highlight diff --git a/lib/gitlab/diff/image_point.rb b/lib/gitlab/diff/image_point.rb index 1f157354ea4..a3ce032f8e2 100644 --- a/lib/gitlab/diff/image_point.rb +++ b/lib/gitlab/diff/image_point.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class ImagePoint diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb index 72d5ec547da..5815d1bae4a 100644 --- a/lib/gitlab/diff/inline_diff.rb +++ b/lib/gitlab/diff/inline_diff.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class InlineDiff @@ -71,7 +73,7 @@ module Gitlab def find_changed_line_pairs(lines) # Prefixes of all diff lines, indicating their types # For example: `" - + -+ ---+++ --+ -++"` - line_prefixes = lines.each_with_object("") { |line, s| s << (line[0] || ' ') }.gsub(/[^ +-]/, ' ') + line_prefixes = lines.each_with_object(+"") { |line, s| s << (line[0] || ' ') }.gsub(/[^ +-]/, ' ') changed_line_pairs = [] line_prefixes.scan(LINE_PAIRS_PATTERN) do diff --git a/lib/gitlab/diff/inline_diff_markdown_marker.rb b/lib/gitlab/diff/inline_diff_markdown_marker.rb index c2a2eb15931..3c536c43a9e 100644 --- a/lib/gitlab/diff/inline_diff_markdown_marker.rb +++ b/lib/gitlab/diff/inline_diff_markdown_marker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class InlineDiffMarkdownMarker < Gitlab::StringRangeMarker diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb index 81e91ea0ab7..1bbde1ffd2a 100644 --- a/lib/gitlab/diff/inline_diff_marker.rb +++ b/lib/gitlab/diff/inline_diff_marker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class InlineDiffMarker < Gitlab::StringRangeMarker diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index 5b67cd46c48..74fed7c4b1b 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class Line diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb index cf71d47df8e..fba7bff4781 100644 --- a/lib/gitlab/diff/line_mapper.rb +++ b/lib/gitlab/diff/line_mapper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # When provided a diff for a specific file, maps old line numbers to new line # numbers and back, to find out where a specific line in a file was moved by the # changes. diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb index 0cb26fa45c8..77b65fea726 100644 --- a/lib/gitlab/diff/parallel_diff.rb +++ b/lib/gitlab/diff/parallel_diff.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class ParallelDiff diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb index 7ae7ed286ed..4a47e4b80b6 100644 --- a/lib/gitlab/diff/parser.rb +++ b/lib/gitlab/diff/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Diff class Parser diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index f967494199e..9c4d9377593 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Defines a specific location, identified by paths line numbers and image coordinates, # within a specific diff, identified by start, head and base commit ids. module Gitlab diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb index 8457e0c4cb6..af3df820422 100644 --- a/lib/gitlab/diff/position_tracer.rb +++ b/lib/gitlab/diff/position_tracer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Finds the diff position in the new diff that corresponds to the same location # specified by the provided position in the old diff. module Gitlab diff --git a/lib/gitlab/downtime_check/message.rb b/lib/gitlab/downtime_check/message.rb index 543e62794c5..ec38bd769a3 100644 --- a/lib/gitlab/downtime_check/message.rb +++ b/lib/gitlab/downtime_check/message.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab class DowntimeCheck class Message @@ -18,13 +20,13 @@ module Gitlab def to_s label = offline ? OFFLINE : ONLINE - message = "[#{label}]: #{path}" + message = ["[#{label}]: #{path}"] if reason? - message += ":\n\n#{reason}\n\n" + message << ":\n\n#{reason}\n\n" end - message + message.join end def reason? diff --git a/lib/gitlab/email/attachment_uploader.rb b/lib/gitlab/email/attachment_uploader.rb index 83440ae227d..a826519b2dd 100644 --- a/lib/gitlab/email/attachment_uploader.rb +++ b/lib/gitlab/email/attachment_uploader.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email class AttachmentUploader diff --git a/lib/gitlab/email/hook/additional_headers_interceptor.rb b/lib/gitlab/email/hook/additional_headers_interceptor.rb index 064cb5e659a..aa2ef76069b 100644 --- a/lib/gitlab/email/hook/additional_headers_interceptor.rb +++ b/lib/gitlab/email/hook/additional_headers_interceptor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email module Hook diff --git a/lib/gitlab/email/hook/delivery_metrics_observer.rb b/lib/gitlab/email/hook/delivery_metrics_observer.rb index 1c2985f6045..c7af485fcc5 100644 --- a/lib/gitlab/email/hook/delivery_metrics_observer.rb +++ b/lib/gitlab/email/hook/delivery_metrics_observer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email module Hook diff --git a/lib/gitlab/email/hook/disable_email_interceptor.rb b/lib/gitlab/email/hook/disable_email_interceptor.rb index 7bb8b53f0c8..6b6b1d85109 100644 --- a/lib/gitlab/email/hook/disable_email_interceptor.rb +++ b/lib/gitlab/email/hook/disable_email_interceptor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email module Hook diff --git a/lib/gitlab/email/hook/email_template_interceptor.rb b/lib/gitlab/email/hook/email_template_interceptor.rb index be0c4dd862e..13f8db2051d 100644 --- a/lib/gitlab/email/hook/email_template_interceptor.rb +++ b/lib/gitlab/email/hook/email_template_interceptor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email module Hook diff --git a/lib/gitlab/email/html_parser.rb b/lib/gitlab/email/html_parser.rb index 50559a48973..77f299bcade 100644 --- a/lib/gitlab/email/html_parser.rb +++ b/lib/gitlab/email/html_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email class HTMLParser diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index cd9d3a6483f..ec412e7a8b1 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Email module Message @@ -116,7 +118,7 @@ module Gitlab end def subject - subject_text = '[Git]' + subject_text = ['[Git]'] subject_text << "[#{project.full_path}]" subject_text << "[#{ref_name}]" if @action == :push subject_text << ' ' @@ -134,6 +136,8 @@ module Gitlab subject_action[0] = subject_action[0].capitalize subject_text << "#{subject_action} #{ref_type} #{ref_name}" end + + subject_text.join end end end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 3a689967a64..d28f6b301fa 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency 'gitlab/email/handler' # Inspired in great part by Discourse's Email::Receiver diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index ae6b84607d6..2743f011ca6 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Inspired in great part by Discourse's Email::Receiver module Gitlab module Email diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index d5d35dbd97f..0341f930b9c 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module EtagCaching class Middleware diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb index 75167a6b088..08e30214b46 100644 --- a/lib/gitlab/etag_caching/router.rb +++ b/lib/gitlab/etag_caching/router.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module EtagCaching class Router diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 21172ff8d93..2395e7be026 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module EtagCaching class Store diff --git a/lib/gitlab/kubernetes/helm/base_command.rb b/lib/gitlab/kubernetes/helm/base_command.rb index 008cba9d33c..f2ce24fefa1 100644 --- a/lib/gitlab/kubernetes/helm/base_command.rb +++ b/lib/gitlab/kubernetes/helm/base_command.rb @@ -10,7 +10,7 @@ module Gitlab def generate_script <<~HEREDOC - set -eo pipefail + set -xeo pipefail HEREDOC end diff --git a/lib/gitlab/kubernetes/helm/init_command.rb b/lib/gitlab/kubernetes/helm/init_command.rb index c7046a9ea75..6691080deca 100644 --- a/lib/gitlab/kubernetes/helm/init_command.rb +++ b/lib/gitlab/kubernetes/helm/init_command.rb @@ -45,7 +45,7 @@ module Gitlab def init_helm_command command = %w[helm init] + init_command_flags - command.shelljoin + " >/dev/null\n" + command.shelljoin end def init_command_flags diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb index 55add06bdb4..ff1c1657b98 100644 --- a/lib/gitlab/kubernetes/helm/install_command.rb +++ b/lib/gitlab/kubernetes/helm/install_command.rb @@ -35,7 +35,7 @@ module Gitlab private def init_command - 'helm init --client-only >/dev/null' + 'helm init --client-only' end def repository_command @@ -43,13 +43,13 @@ module Gitlab end def repository_update_command - 'helm repo update >/dev/null' if repository + 'helm repo update' if repository end def install_command command = ['helm', 'install', chart] + install_command_flags - command.shelljoin + " >/dev/null\n" + command.shelljoin end def preinstall_command diff --git a/lib/gitlab/kubernetes/helm/upgrade_command.rb b/lib/gitlab/kubernetes/helm/upgrade_command.rb index 74188046739..b36315f7a82 100644 --- a/lib/gitlab/kubernetes/helm/upgrade_command.rb +++ b/lib/gitlab/kubernetes/helm/upgrade_command.rb @@ -36,7 +36,7 @@ module Gitlab private def init_command - 'helm init --client-only >/dev/null' + 'helm init --client-only' end def repository_command @@ -50,7 +50,7 @@ module Gitlab " --namespace #{::Gitlab::Kubernetes::Helm::NAMESPACE}" \ " -f /data/helm/#{name}/config/values.yaml" - "helm upgrade #{name} #{chart}#{upgrade_flags} >/dev/null\n" + "helm upgrade #{name} #{chart}#{upgrade_flags}" end def optional_version_flag diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3d66b092143..d3e1a51370e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1364,6 +1364,15 @@ msgstr "" msgid "ClusterIntegration|Add Kubernetes cluster" msgstr "" +msgid "ClusterIntegration|Add a Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Adding a Kubernetes cluster to your group will automatically share the cluster across all your projects. Use review apps, deploy your applications, and easily run your pipelines for all projects using the same cluster." +msgstr "" + +msgid "ClusterIntegration|Adding an integration to your group will share the cluster across all your projects." +msgstr "" + msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" msgstr "" @@ -1466,6 +1475,9 @@ msgstr "" msgid "ClusterIntegration|Google Kubernetes Engine project" msgstr "" +msgid "ClusterIntegration|Group cluster" +msgstr "" + msgid "ClusterIntegration|Helm Tiller" msgstr "" @@ -1520,9 +1532,6 @@ msgstr "" msgid "ClusterIntegration|Kubernetes cluster details" msgstr "" -msgid "ClusterIntegration|Kubernetes cluster integration" -msgstr "" - msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." msgstr "" @@ -1532,7 +1541,7 @@ msgstr "" msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" msgstr "" -msgid "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}" +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" @@ -1541,10 +1550,13 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}." msgstr "" -msgid "ClusterIntegration|Learn more about %{help_link_start}Kubernetes%{help_link_end}." +msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." msgstr "" -msgid "ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}." +msgid "ClusterIntegration|Learn more about Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Learn more about group Kubernetes clusters" msgstr "" msgid "ClusterIntegration|Machine type" @@ -1589,6 +1601,9 @@ msgstr "" msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed." msgstr "" +msgid "ClusterIntegration|Project cluster" +msgstr "" + msgid "ClusterIntegration|Project namespace" msgstr "" @@ -1679,9 +1694,6 @@ msgstr "" msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters." msgstr "" -msgid "ClusterIntegration|Toggle Kubernetes Cluster" -msgstr "" - msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb new file mode 100644 index 00000000000..43894372cf5 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Push over HTTP using Git protocol version 2', :requires_git_protocol_v2 do + it 'user pushes to the repository' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + # Create a project to push to + project = Resource::Project.fabricate! do |project| + project.name = 'git-protocol-project' + end + + file_name = 'README.md' + file_content = 'Test Git protocol v2' + git_protocol = '2' + git_protocol_reported = nil + + # Use Git to clone the project, push a file to it, and then check the + # supported Git protocol + Git::Repository.perform do |repository| + username = 'GitLab QA' + email = 'root@gitlab.com' + + repository.uri = project.repository_http_location.uri + repository.use_default_credentials + repository.clone + repository.configure_identity(username, email) + + git_protocol_reported = repository.push_with_git_protocol( + git_protocol, + file_name, + file_content) + end + + project.visit! + Page::Project::Show.perform(&:wait_for_push) + + # Check that the push worked + expect(page).to have_content(file_name) + expect(page).to have_content(file_content) + + # And check that the correct Git protocol was used + expect(git_protocol_reported).to eq(git_protocol) + end + end + end +end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index d372bcbdab1..016682e5a3d 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -126,6 +126,9 @@ function deploy() { delete cleanup fi + + create_secret + helm repo add gitlab https://charts.gitlab.io/ helm dep update . diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb index 6d0483f0032..98946e4287b 100644 --- a/spec/controllers/boards/issues_controller_spec.rb +++ b/spec/controllers/boards/issues_controller_spec.rb @@ -50,7 +50,7 @@ describe Boards::IssuesController do parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('entities/issue_boards') + expect(response).to match_response_schema('issues') expect(parsed_response['issues'].length).to eq 2 expect(development.issues.map(&:relative_position)).not_to include(nil) end @@ -121,7 +121,7 @@ describe Boards::IssuesController do parsed_response = JSON.parse(response.body) - expect(response).to match_response_schema('entities/issue_boards') + expect(response).to match_response_schema('issues') expect(parsed_response['issues'].length).to eq 2 end end @@ -168,7 +168,7 @@ describe Boards::IssuesController do it 'returns the created issue' do create_issue user: user, board: board, list: list1, title: 'New issue' - expect(response).to match_response_schema('entities/issue_board') + expect(response).to match_response_schema('issue') end end diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index 767fba7fd58..4f1f6bb31f3 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -28,8 +28,9 @@ describe SendFileUpload do describe '#send_upload' do let(:controller) { controller_class.new } let(:temp_file) { Tempfile.new('test') } + let(:params) { {} } - subject { controller.send_upload(uploader) } + subject { controller.send_upload(uploader, **params) } before do FileUtils.touch(temp_file) @@ -52,7 +53,7 @@ describe SendFileUpload do end context 'with attachment' do - let(:send_attachment) { controller.send_upload(uploader, attachment: 'test.js') } + let(:params) { { attachment: 'test.js' } } it 'sends a file with content-type of text/plain' do expected_params = { @@ -62,7 +63,7 @@ describe SendFileUpload do } expect(controller).to receive(:send_file).with(uploader.path, expected_params) - send_attachment + subject end context 'with a proxied file in object storage' do @@ -83,7 +84,7 @@ describe SendFileUpload do expect(controller).to receive(:headers) { headers } expect(controller).to receive(:head).with(:ok) - send_attachment + subject end end end @@ -95,11 +96,7 @@ describe SendFileUpload do uploader.store!(temp_file) end - context 'and proxying is enabled' do - before do - allow(Gitlab.config.uploads.object_store).to receive(:proxy_download) { true } - end - + shared_examples 'proxied file' do it 'sends a file' do headers = double expect(Gitlab::Workhorse).not_to receive(:send_url).with(/response-content-disposition/) @@ -115,6 +112,14 @@ describe SendFileUpload do end end + context 'and proxying is enabled' do + before do + allow(Gitlab.config.uploads.object_store).to receive(:proxy_download) { true } + end + + it_behaves_like 'proxied file' + end + context 'and proxying is disabled' do before do allow(Gitlab.config.uploads.object_store).to receive(:proxy_download) { false } @@ -125,6 +130,12 @@ describe SendFileUpload do subject end + + context 'with proxy requested' do + let(:params) { { proxy: true } } + + it_behaves_like 'proxied file' + end end end end diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb new file mode 100644 index 00000000000..68a798542b6 --- /dev/null +++ b/spec/controllers/groups/clusters/applications_controller_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Groups::Clusters::ApplicationsController do + include AccessMatchersForController + + def current_application + Clusters::Cluster::APPLICATIONS[application] + end + + describe 'POST create' do + let(:cluster) { create(:cluster, :group, :provided_by_gcp) } + let(:group) { cluster.group } + let(:application) { 'helm' } + let(:params) { { application: application, id: cluster.id } } + + describe 'functionality' do + let(:user) { create(:user) } + + before do + group.add_maintainer(user) + sign_in(user) + end + + it 'schedule an application installation' do + expect(ClusterInstallAppWorker).to receive(:perform_async).with(application, anything).once + + expect { go }.to change { current_application.count } + expect(response).to have_http_status(:no_content) + expect(cluster.application_helm).to be_scheduled + end + + context 'when cluster do not exists' do + before do + cluster.destroy! + end + + it 'return 404' do + expect { go }.not_to change { current_application.count } + expect(response).to have_http_status(:not_found) + end + end + + context 'when application is unknown' do + let(:application) { 'unkwnown-app' } + + it 'return 404' do + go + + expect(response).to have_http_status(:not_found) + end + end + + context 'when application is already installing' do + before do + create(:clusters_applications_helm, :installing, cluster: cluster) + end + + it 'returns 400' do + go + + expect(response).to have_http_status(:bad_request) + end + end + end + + describe 'security' do + before do + allow(ClusterInstallAppWorker).to receive(:perform_async) + end + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + post :create, params.merge(group_id: group) + end + end +end diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb new file mode 100644 index 00000000000..6e130f830a2 --- /dev/null +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -0,0 +1,574 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Groups::ClustersController do + include AccessMatchersForController + include GoogleApi::CloudPlatformHelpers + + set(:group) { create(:group) } + + let(:user) { create(:user) } + + before do + group.add_maintainer(user) + sign_in(user) + end + + describe 'GET index' do + def go(params = {}) + get :index, params.reverse_merge(group_id: group) + end + + context 'when feature flag is not enabled' do + before do + stub_feature_flags(group_clusters: false) + end + + it 'renders 404' do + go + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when feature flag is enabled' do + before do + stub_feature_flags(group_clusters: true) + end + + describe 'functionality' do + context 'when group has one or more clusters' do + let(:group) { create(:group) } + + let!(:enabled_cluster) do + create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) + end + + let!(:disabled_cluster) do + create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) + end + + it 'lists available clusters' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index) + expect(assigns(:clusters)).to match_array([enabled_cluster, disabled_cluster]) + end + + context 'when page is specified' do + let(:last_page) { group.clusters.page.total_pages } + + before do + allow(Clusters::Cluster).to receive(:paginates_per).and_return(1) + create_list(:cluster, 2, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) + end + + it 'redirects to the page' do + go(page: last_page) + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:clusters).current_page).to eq(last_page) + end + end + end + + context 'when group does not have a cluster' do + let(:group) { create(:group) } + + it 'returns an empty state page' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index, partial: :empty_state) + expect(assigns(:clusters)).to eq([]) + end + end + end + end + + describe 'security' do + let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET new' do + def go + get :new, group_id: group + end + + describe 'functionality for new cluster' do + context 'when omniauth has been configured' do + let(:key) { 'secret-key' } + let(:session_key_for_redirect_uri) do + GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key) + end + + before do + allow(SecureRandom).to receive(:hex).and_return(key) + end + + it 'has authorize_url' do + go + + expect(assigns(:authorize_url)).to include(key) + expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group)) + end + end + + context 'when omniauth has not configured' do + before do + stub_omniauth_setting(providers: []) + end + + it 'does not have authorize_url' do + go + + expect(assigns(:authorize_url)).to be_nil + end + end + + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'has new object' do + go + + expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(@valid_gcp_token).to be_falsey } + end + + context 'when access token is not stored in session' do + it { expect(@valid_gcp_token).to be_falsey } + end + end + + describe 'functionality for existing cluster' do + it 'has new object' do + go + + expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'POST create for new cluster' do + let(:legacy_abac_param) { 'true' } + let(:params) do + { + cluster: { + name: 'new-cluster', + provider_gcp_attributes: { + gcp_project_id: 'gcp-project-12345', + legacy_abac: legacy_abac_param + } + } + } + end + + def go + post :create_gcp, params.merge(group_id: group) + end + + describe 'functionality' do + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + + cluster = group.clusters.first + + expect(response).to redirect_to(group_cluster_path(group, cluster)) + expect(cluster).to be_gcp + expect(cluster).to be_kubernetes + expect(cluster.provider_gcp).to be_legacy_abac + end + + context 'when legacy_abac param is false' do + let(:legacy_abac_param) { 'false' } + + it 'creates a new cluster with legacy_abac_disabled' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + expect(group.clusters.first.provider_gcp).not_to be_legacy_abac + end + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(@valid_gcp_token).to be_falsey } + end + + context 'when access token is not stored in session' do + it { expect(@valid_gcp_token).to be_falsey } + end + end + + describe 'security' do + before do + allow_any_instance_of(described_class) + .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) + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create) do + OpenStruct.new( + self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123', + status: 'RUNNING' + ) + end + + allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) + end + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'POST create for existing cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test' + } + } + } + end + + def go + post :create_user, params.merge(group_id: group) + end + + describe 'functionality' do + context 'when creates a cluster' do + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Platforms::Kubernetes.count } + + cluster = group.clusters.first + + expect(response).to redirect_to(group_cluster_path(group, cluster)) + expect(cluster).to be_user + expect(cluster).to be_kubernetes + end + end + + context 'when creates a RBAC-enabled cluster' do + let(:params) do + { + cluster: { + name: 'new-cluster', + platform_kubernetes_attributes: { + api_url: 'http://my-url', + token: 'test', + authorization_type: 'rbac' + } + } + } + end + + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Platforms::Kubernetes.count } + + cluster = group.clusters.first + + expect(response).to redirect_to(group_cluster_path(group, cluster)) + expect(cluster).to be_user + expect(cluster).to be_kubernetes + expect(cluster).to be_platform_kubernetes_rbac + end + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET cluster_status' do + let(:cluster) { create(:cluster, :providing_by_gcp, cluster_type: :group_type, groups: [group]) } + + def go + get :cluster_status, + group_id: group.to_param, + id: cluster, + format: :json + end + + describe 'functionality' do + it 'responds with matching schema' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('cluster_status') + end + + it 'invokes schedule_status_update on each application' do + expect_any_instance_of(Clusters::Applications::Ingress).to receive(:schedule_status_update) + + go + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'GET show' do + let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } + + def go + get :show, + group_id: group, + id: cluster + end + + describe 'functionality' do + it 'renders view' do + go + + expect(response).to have_gitlab_http_status(:ok) + expect(assigns(:cluster)).to eq(cluster) + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'PUT update' do + def go(format: :html) + put :update, params.merge( + group_id: group.to_param, + id: cluster, + format: format + ) + end + + let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) } + + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name' + } + } + end + + it 'updates and redirects back to show page' do + go + + cluster.reload + expect(response).to redirect_to(group_cluster_path(group, cluster)) + expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + end + + context 'when format is json' do + context 'when changing parameters' do + context 'when valid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name' + } + } + end + + it 'updates and redirects back to show page' do + go(format: :json) + + cluster.reload + expect(response).to have_http_status(:no_content) + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + end + end + + context 'when invalid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + name: '' + } + } + end + + it 'rejects changes' do + go(format: :json) + + expect(response).to have_http_status(:bad_request) + end + end + end + end + + describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + describe 'DELETE destroy' do + let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) } + + def go + delete :destroy, + group_id: group, + id: cluster + end + + describe 'functionality' do + context 'when cluster is provided by GCP' do + context 'when cluster is created' do + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Platforms::Kubernetes.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(-1) + + expect(response).to redirect_to(group_clusters_path(group)) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + + context 'when cluster is being created' do + let!(:cluster) { create(:cluster, :providing_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) } + + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(-1) + + expect(response).to redirect_to(group_clusters_path(group)) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + end + + context 'when cluster is provided by user' do + let!(:cluster) { create(:cluster, :provided_by_user, :production_environment, cluster_type: :group_type, groups: [group]) } + + it 'destroys and redirects back to clusters list' do + expect { go } + .to change { Clusters::Cluster.count }.by(-1) + .and change { Clusters::Platforms::Kubernetes.count }.by(-1) + .and change { Clusters::Providers::Gcp.count }.by(0) + + expect(response).to redirect_to(group_clusters_path(group)) + expect(flash[:notice]).to eq('Kubernetes cluster integration was successfully removed.') + end + end + end + + describe 'security' do + set(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group]) } + + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(group) } + it { expect { go }.to be_allowed_for(:maintainer).of(group) } + it { expect { go }.to be_denied_for(:developer).of(group) } + it { expect { go }.to be_denied_for(:reporter).of(group) } + it { expect { go }.to be_denied_for(:guest).of(group) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + end + + context 'no group_id param' do + it 'does not respond to any action without group_id param' do + expect { get :index }.to raise_error(ActionController::UrlGenerationError) + end + end +end diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 6091185e252..b3c8d6a954e 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -47,14 +47,37 @@ describe Projects::ArtifactsController do context 'when codequality file type is supplied' do let(:file_type) { 'codequality' } - before do - create(:ci_job_artifact, :codequality, job: job) + context 'when file is stored locally' do + before do + create(:ci_job_artifact, :codequality, job: job) + end + + it 'sends the codequality report' do + expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original + + download_artifact(file_type: file_type) + end end - it 'sends the codequality report' do - expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original + context 'when file is stored remotely' do + before do + stub_artifacts_object_storage + create(:ci_job_artifact, :remote_store, :codequality, job: job) + end + + it 'sends the codequality report' do + expect(controller).to receive(:redirect_to).and_call_original - download_artifact(file_type: file_type) + download_artifact(file_type: file_type) + end + + context 'when proxied' do + it 'sends the codequality report' do + expect(Gitlab::Workhorse).to receive(:send_url).and_call_original + + download_artifact(file_type: file_type, proxy: true) + end + end end end end diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 04aece26590..483222363bb 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -122,7 +122,7 @@ describe Projects::ClustersController do it 'has new object' do go - expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::Cluster) + expect(assigns(:gcp_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end end @@ -143,7 +143,7 @@ describe Projects::ClustersController do it 'has new object' do go - expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::Cluster) + expect(assigns(:user_cluster)).to be_an_instance_of(Clusters::ClusterPresenter) end end @@ -396,20 +396,6 @@ describe Projects::ClustersController do end describe 'PUT update' do - let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - - let(:params) do - { - cluster: { - enabled: false, - name: 'my-new-cluster-name', - platform_kubernetes_attributes: { - namespace: 'my-namespace' - } - } - } - end - def go(format: :html) put :update, params.merge(namespace_id: project.namespace.to_param, project_id: project.to_param, @@ -423,105 +409,73 @@ describe Projects::ClustersController do 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 - - cluster.reload - expect(response).to redirect_to(project_cluster_path(project, cluster)) - expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') - expect(cluster.enabled).to be_falsey - end - - it "does not change cluster name" do - go - - cluster.reload - expect(cluster.name).to eq('test-cluster') - end - - context 'when cluster is being created' do - let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } + let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } - it "rejects changes" do - go - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to render_template(:show) - expect(cluster.enabled).to be_truthy - end - end - end - - context 'when cluster is provided by user' do - let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) } - - let(:params) do - { - cluster: { - enabled: false, - name: 'my-new-cluster-name', - platform_kubernetes_attributes: { - namespace: 'my-namespace' - } + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name', + platform_kubernetes_attributes: { + namespace: 'my-namespace' } } - end + } + end - it "updates and redirects back to show page" do - go + it "updates and redirects back to show page" do + go - cluster.reload - expect(response).to redirect_to(project_cluster_path(project, cluster)) - expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') - expect(cluster.enabled).to be_falsey - expect(cluster.name).to eq('my-new-cluster-name') - expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') - end + cluster.reload + expect(response).to redirect_to(project_cluster_path(project, cluster)) + expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') + end - context 'when format is json' do - context 'when changing parameters' do - context 'when valid parameters are used' do - let(:params) do - { - cluster: { - enabled: false, - name: 'my-new-cluster-name', - platform_kubernetes_attributes: { - namespace: 'my-namespace' - } + context 'when format is json' do + context 'when changing parameters' do + context 'when valid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + name: 'my-new-cluster-name', + platform_kubernetes_attributes: { + namespace: 'my-namespace' } } - end + } + end - it "updates and redirects back to show page" do - go(format: :json) + it "updates and redirects back to show page" do + go(format: :json) - cluster.reload - expect(response).to have_http_status(:no_content) - expect(cluster.enabled).to be_falsey - expect(cluster.name).to eq('my-new-cluster-name') - expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') - end + cluster.reload + expect(response).to have_http_status(:no_content) + expect(cluster.enabled).to be_falsey + expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end + end - context 'when invalid parameters are used' do - let(:params) do - { - cluster: { - enabled: false, - platform_kubernetes_attributes: { - namespace: 'my invalid namespace #@' - } + context 'when invalid parameters are used' do + let(:params) do + { + cluster: { + enabled: false, + platform_kubernetes_attributes: { + namespace: 'my invalid namespace #@' } } - end + } + end - it "rejects changes" do - go(format: :json) + it "rejects changes" do + go(format: :json) - expect(response).to have_http_status(:bad_request) - end + expect(response).to have_http_status(:bad_request) end end end diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb index 4a0d1b181ea..8169c457ab7 100644 --- a/spec/factories/clusters/platforms/kubernetes.rb +++ b/spec/factories/clusters/platforms/kubernetes.rb @@ -10,7 +10,7 @@ FactoryBot.define do username 'xxxxxx' password 'xxxxxx' - after(:create) do |platform_kubernetes, evaluator| + before(:create) do |platform_kubernetes, evaluator| pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) platform_kubernetes.ca_cert = File.read(pem_file) end diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index f13c35c00d3..a85e7333ba8 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -35,37 +35,6 @@ describe 'Clusters', :js do expect(page).to have_selector('.gl-responsive-table-row', count: 2) end - context 'inline update of cluster' do - it 'user can update cluster' do - expect(page).to have_selector('.js-project-feature-toggle') - end - - context 'with successful request' do - it 'user sees updated cluster' do - expect do - page.find('.js-project-feature-toggle').click - wait_for_requests - end.to change { cluster.reload.enabled } - - expect(page).not_to have_selector('.is-checked') - expect(cluster.reload).not_to be_enabled - end - end - - context 'with failed request' do - it 'user sees not update cluster and error message' do - expect_any_instance_of(Clusters::UpdateService).to receive(:execute).and_call_original - allow_any_instance_of(Clusters::Cluster).to receive(:valid?) { false } - - page.find('.js-project-feature-toggle').click - - expect(page).to have_content('Something went wrong on our end.') - expect(page).to have_selector('.is-checked') - expect(cluster.reload).to be_enabled - end - end - end - context 'when user clicks on a cluster' do before do click_link cluster.name diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb index 69ebdddaeec..0295f588326 100644 --- a/spec/features/user_sorts_things_spec.rb +++ b/spec/features/user_sorts_things_spec.rb @@ -6,7 +6,7 @@ require "spec_helper" # All those specs are moved out to this spec intentionally to keep them all in one place. describe "User sorts things" do include Spec::Support::Helpers::Features::SortingHelpers - include Helpers::DashboardHelper + include DashboardHelper set(:project) { create(:project_empty_repo, :public) } set(:current_user) { create(:user) } # Using `current_user` instead of just `user` because of the hardoced call in `assigned_mrs_dashboard_path` which is used below. diff --git a/spec/fixtures/api/schemas/entities/issue_boards.json b/spec/fixtures/api/schemas/entities/issue_boards.json deleted file mode 100644 index 0ac1d9468c8..00000000000 --- a/spec/fixtures/api/schemas/entities/issue_boards.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "object", - "required" : [ - "issues", - "size" - ], - "properties" : { - "issues": { - "type": "array", - "items": { "$ref": "issue_board.json" } - }, - "size": { "type": "integer" } - }, - "additionalProperties": false -} diff --git a/spec/javascripts/vue_shared/components/clipboard_button_spec.js b/spec/javascripts/vue_shared/components/clipboard_button_spec.js index 2f7ea077b54..fd17349d48f 100644 --- a/spec/javascripts/vue_shared/components/clipboard_button_spec.js +++ b/spec/javascripts/vue_shared/components/clipboard_button_spec.js @@ -27,8 +27,6 @@ describe('clipboard button', () => { it('should have a tooltip with default values', () => { expect(vm.$el.getAttribute('data-original-title')).toEqual('Copy this value into Clipboard!'); - expect(vm.$el.getAttribute('data-placement')).toEqual('top'); - expect(vm.$el.getAttribute('data-container')).toEqual(null); }); it('should render provided classname', () => { diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb index 72dc1817936..4a3b9d4bf6a 100644 --- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Kubernetes::Helm::InitCommand do let(:commands) do <<~EOS - helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem >/dev/null + helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem EOS end @@ -22,7 +22,7 @@ describe Gitlab::Kubernetes::Helm::InitCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem --service-account tiller >/dev/null + helm init --tiller-tls --tiller-tls-verify --tls-ca-cert /data/helm/helm/config/ca.pem --tiller-tls-cert /data/helm/helm/config/cert.pem --tiller-tls-key /data/helm/helm/config/key.pem --service-account tiller EOS end end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index ed879350004..2b7e3ea6def 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -26,9 +26,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_comand} EOS end @@ -43,7 +43,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps - -f /data/helm/app-name/config/values.yaml >/dev/null + -f /data/helm/app-name/config/values.yaml EOS end end @@ -54,9 +54,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_command} EOS end @@ -72,7 +72,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --version 1.2.3 --set rbac.create\\=true,rbac.enabled\\=true --namespace gitlab-managed-apps - -f /data/helm/app-name/config/values.yaml >/dev/null + -f /data/helm/app-name/config/values.yaml EOS end end @@ -84,7 +84,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only #{helm_install_command} EOS end @@ -99,7 +99,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps - -f /data/helm/app-name/config/values.yaml >/dev/null + -f /data/helm/app-name/config/values.yaml EOS end end @@ -111,9 +111,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_command} EOS end @@ -122,7 +122,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.strip /bin/date /bin/true - helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null + helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS end end @@ -134,17 +134,16 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_command} EOS end let(:helm_install_command) do <<~EOS.strip - helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml >/dev/null - + helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml /bin/date /bin/false EOS @@ -158,9 +157,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_command} EOS end @@ -171,7 +170,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --name app-name --version 1.2.3 --namespace gitlab-managed-apps - -f /data/helm/app-name/config/values.yaml >/dev/null + -f /data/helm/app-name/config/values.yaml EOS end end @@ -183,9 +182,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add app-name https://repository.example.com - helm repo update >/dev/null + helm repo update #{helm_install_command} EOS end @@ -199,7 +198,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --namespace gitlab-managed-apps - -f /data/helm/app-name/config/values.yaml >/dev/null + -f /data/helm/app-name/config/values.yaml EOS end end diff --git a/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb index 3dabf04413e..9c9fc91ef3c 100644 --- a/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb @@ -21,8 +21,8 @@ describe Gitlab::Kubernetes::Helm::UpgradeCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null - helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + helm init --client-only + helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml EOS end end @@ -33,8 +33,8 @@ describe Gitlab::Kubernetes::Helm::UpgradeCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null - helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + helm init --client-only + helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml EOS end end @@ -56,9 +56,9 @@ describe Gitlab::Kubernetes::Helm::UpgradeCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null + helm init --client-only helm repo add #{application.name} #{application.repository} - helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml EOS end end @@ -70,8 +70,8 @@ describe Gitlab::Kubernetes::Helm::UpgradeCommand do it_behaves_like 'helm commands' do let(:commands) do <<~EOS - helm init --client-only >/dev/null - helm upgrade #{application.name} #{application.chart} --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + helm init --client-only + helm upgrade #{application.name} #{application.chart} --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml EOS end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 10b9ca1a778..98d7e799d67 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -343,4 +343,26 @@ describe Clusters::Cluster do it { is_expected.to eq(false) } end end + + describe '#allow_user_defined_namespace?' do + let(:cluster) { create(:cluster, :provided_by_gcp) } + + subject { cluster.allow_user_defined_namespace? } + + context 'project type cluster' do + it { is_expected.to be_truthy } + end + + context 'group type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + it { is_expected.to be_falsey } + end + + context 'instance type cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + + it { is_expected.to be_falsey } + end + end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 2bcccc8184a..f5d261c4e9d 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -58,6 +58,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to be_truthy } end + + context 'for group cluster' do + let(:namespace) { 'namespace-123' } + let(:cluster) { build(:cluster, :group, :provided_by_user) } + let(:kubernetes) { cluster.platform_kubernetes } + + before do + kubernetes.namespace = namespace + end + + it { is_expected.to be_falsey } + end end context 'when validates api_url' do diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb index 5713106418d..debc02fa51f 100644 --- a/spec/models/concerns/awardable_spec.rb +++ b/spec/models/concerns/awardable_spec.rb @@ -37,8 +37,8 @@ describe Awardable do create(:award_emoji, awardable: issue3, name: "star", user: award_emoji.user) create(:award_emoji, awardable: issue3, name: "star", user: award_emoji2.user) - expect(Issue.awarded(award_emoji.user)).to contain_exactly(issue, issue3) - expect(Issue.awarded(award_emoji2.user)).to contain_exactly(issue2, issue3) + expect(Issue.awarded(award_emoji.user)).to eq [issue, issue3] + expect(Issue.awarded(award_emoji2.user)).to eq [issue2, issue3] end end diff --git a/spec/policies/clusters/cluster_policy_spec.rb b/spec/policies/clusters/cluster_policy_spec.rb index ced969830d8..b2f0ca1bc30 100644 --- a/spec/policies/clusters/cluster_policy_spec.rb +++ b/spec/policies/clusters/cluster_policy_spec.rb @@ -24,5 +24,47 @@ describe Clusters::ClusterPolicy, :models do it { expect(policy).to be_allowed :update_cluster } it { expect(policy).to be_allowed :admin_cluster } end + + context 'group cluster' do + let(:cluster) { create(:cluster, :group) } + let(:group) { cluster.group } + let(:project) { create(:project, namespace: group) } + + context 'when group developer' do + before do + group.add_developer(user) + end + + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + + context 'when group maintainer' do + before do + group.add_maintainer(user) + end + + it { expect(policy).to be_allowed :update_cluster } + it { expect(policy).to be_allowed :admin_cluster } + end + + context 'when project maintainer' do + before do + project.add_maintainer(user) + end + + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + + context 'when project developer' do + before do + project.add_developer(user) + end + + it { expect(policy).to be_disallowed :update_cluster } + it { expect(policy).to be_disallowed :admin_cluster } + end + end end end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index 5e583be457e..9d0093e8159 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -21,7 +21,11 @@ describe GroupPolicy do let(:maintainer_permissions) do [ - :create_projects + :create_projects, + :read_cluster, + :create_cluster, + :update_cluster, + :admin_cluster ] end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index b7ec35d6ec5..d6bc67a9d70 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -163,7 +163,7 @@ describe ProjectPolicy do :create_build, :read_build, :update_build, :admin_build, :destroy_build, :create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule, :create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment, - :create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster, + :create_cluster, :read_cluster, :update_cluster, :admin_cluster, :create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment ] @@ -182,7 +182,7 @@ describe ProjectPolicy do :create_build, :read_build, :update_build, :admin_build, :destroy_build, :create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule, :create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment, - :create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster, + :create_cluster, :read_cluster, :update_cluster, :admin_cluster, :create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment ] diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb index 7af181f37d5..72c5eac3ede 100644 --- a/spec/presenters/clusters/cluster_presenter_spec.rb +++ b/spec/presenters/clusters/cluster_presenter_spec.rb @@ -82,5 +82,12 @@ describe Clusters::ClusterPresenter do it { is_expected.to eq(project_cluster_path(project, cluster)) } end + + context 'group_type cluster' do + let(:group) { cluster.group } + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + it { is_expected.to eq(group_cluster_path(group, cluster)) } + end end end diff --git a/spec/presenters/group_clusterable_presenter_spec.rb b/spec/presenters/group_clusterable_presenter_spec.rb new file mode 100644 index 00000000000..205160742bf --- /dev/null +++ b/spec/presenters/group_clusterable_presenter_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe GroupClusterablePresenter do + include Gitlab::Routing.url_helpers + + let(:presenter) { described_class.new(group) } + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + let(:group) { cluster.group } + + 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 + group.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(group_clusters_path(group)) } + end + + describe '#new_path' do + subject { presenter.new_path } + + it { is_expected.to eq(new_group_cluster_path(group)) } + end + + describe '#create_user_clusters_path' do + subject { presenter.create_user_clusters_path } + + it { is_expected.to eq(create_user_group_clusters_path(group)) } + end + + describe '#create_gcp_clusters_path' do + subject { presenter.create_gcp_clusters_path } + + it { is_expected.to eq(create_gcp_group_clusters_path(group)) } + end + + describe '#cluster_status_cluster_path' do + subject { presenter.cluster_status_cluster_path(cluster) } + + it { is_expected.to eq(cluster_status_group_cluster_path(group, 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_group_cluster_path(group, cluster, application)) } + end + + describe '#cluster_path' do + subject { presenter.cluster_path(cluster) } + + it { is_expected.to eq(group_cluster_path(group, cluster)) } + end +end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 2963dea634a..329d069ef3d 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1208,6 +1208,118 @@ describe API::Commits do end end + describe 'POST :id/repository/commits/:sha/revert' do + let(:commit_id) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' } + let(:commit) { project.commit(commit_id) } + let(:branch) { 'master' } + let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/revert" } + + shared_examples_for 'ref revert' do + context 'when ref exists' do + it 'reverts the ref commit' do + post api(route, current_user), branch: branch + + expect(response).to have_gitlab_http_status(201) + expect(response).to match_response_schema('public_api/v4/commit/basic') + + expect(json_response['message']).to eq(commit.revert_message(user)) + expect(json_response['author_name']).to eq(user.name) + expect(json_response['committer_name']).to eq(user.name) + expect(json_response['parent_ids']).to contain_exactly(commit_id) + end + end + + context 'when repository is disabled' do + include_context 'disabled repository' + + it_behaves_like '403 response' do + let(:request) { post api(route, current_user), branch: branch } + end + end + end + + context 'when unauthenticated', 'and project is public' do + let(:project) { create(:project, :public, :repository) } + + it_behaves_like '403 response' do + let(:request) { post api(route), branch: branch } + end + end + + context 'when unauthenticated', 'and project is private' do + it_behaves_like '404 response' do + let(:request) { post api(route), branch: branch } + let(:message) { '404 Project Not Found' } + end + end + + context 'when authenticated', 'as an owner' do + let(:current_user) { user } + + it_behaves_like 'ref revert' + + context 'when ref does not exist' do + let(:commit_id) { 'unknown' } + + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), branch: branch } + let(:message) { '404 Commit Not Found' } + end + end + + context 'when branch is missing' do + it_behaves_like '400 response' do + let(:request) { post api(route, current_user) } + end + end + + context 'when branch is empty' do + ['', ' '].each do |branch| + it_behaves_like '400 response' do + let(:request) { post api(route, current_user), branch: branch } + end + end + end + + context 'when branch does not exist' do + it_behaves_like '404 response' do + let(:request) { post api(route, current_user), branch: 'foo' } + let(:message) { '404 Branch Not Found' } + end + end + + context 'when ref contains a dot' do + let(:commit_id) { branch_with_dot.name } + let(:commit) { project.repository.commit(commit_id) } + + it_behaves_like '400 response' do + let(:request) { post api(route, current_user) } + end + end + end + + context 'when authenticated', 'as a developer' do + let(:current_user) { user } + + before do + project.add_developer(user) + end + + context 'when branch is protected' do + before do + create(:protected_branch, project: project, name: 'feature') + end + + it 'returns 400 if you are not allowed to push to the target branch' do + post api(route, current_user), branch: 'feature' + + expect(response).to have_gitlab_http_status(:forbidden) + expect(json_response['message']).to match(/You are not allowed to push into this branch/) + end + end + end + end + describe 'POST /projects/:id/repository/commits/:sha/comments' do let(:commit) { project.repository.commit } let(:commit_id) { commit.id } diff --git a/spec/serializers/issue_board_entity_spec.rb b/spec/serializers/issue_board_entity_spec.rb deleted file mode 100644 index 06d9d3657e6..00000000000 --- a/spec/serializers/issue_board_entity_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe IssueBoardEntity do - let(:project) { create(:project) } - let(:resource) { create(:issue, project: project) } - let(:user) { create(:user) } - - let(:request) { double('request', current_user: user) } - - subject { described_class.new(resource, request: request).as_json } - - it 'has basic attributes' do - expect(subject).to include(:id, :iid, :title, :confidential, :due_date, :project_id, :relative_position, - :project, :labels) - end - - it 'has path and endpoints' do - expect(subject).to include(:reference_path, :real_path, :issue_sidebar_endpoint, - :toggle_subscription_endpoint, :assignable_labels_endpoint) - end -end diff --git a/spec/serializers/issue_serializer_spec.rb b/spec/serializers/issue_serializer_spec.rb index e8c46c0cdee..75578816e75 100644 --- a/spec/serializers/issue_serializer_spec.rb +++ b/spec/serializers/issue_serializer_spec.rb @@ -24,12 +24,4 @@ describe IssueSerializer do expect(json_entity).to match_schema('entities/issue_sidebar') end end - - context 'board issue serialization' do - let(:serializer) { 'board' } - - it 'matches board issue json schema' do - expect(json_entity).to match_schema('entities/issue_board') - end - end end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb index a9985133b93..0bd7719345e 100644 --- a/spec/services/clusters/applications/create_service_spec.rb +++ b/spec/services/clusters/applications/create_service_spec.rb @@ -60,14 +60,6 @@ describe Clusters::Applications::CreateService do end end - context 'invalid application' do - let(:params) { { application: 'non-existent' } } - - it 'raises an error' do - expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError) - end - end - context 'knative application' do let(:params) do { @@ -100,5 +92,39 @@ describe Clusters::Applications::CreateService do expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError) end end + + context 'group cluster' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + + using RSpec::Parameterized::TableSyntax + + before do + allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute) + end + + where(:application, :association, :allowed) do + 'helm' | :application_helm | true + 'ingress' | :application_ingress | true + 'runner' | :application_runner | false + 'jupyter' | :application_jupyter | false + 'prometheus' | :application_prometheus | false + end + + with_them do + let(:params) { { application: application } } + + it 'executes for each application' do + if allowed + expect do + subject + + cluster.reload + end.to change(cluster, association) + else + expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError) + end + end + end + end end end diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index a1b20c61116..73f9be242a3 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -62,5 +62,32 @@ describe Clusters::UpdateService do expect(cluster.errors[:"platform_kubernetes.namespace"]).to be_present end end + + context 'when cluster is provided by GCP' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + + let(:params) do + { + name: 'my-new-name' + } + end + + it 'does not change cluster name' do + is_expected.to eq(false) + + cluster.reload + expect(cluster.name).to eq('test-cluster') + end + + context 'when cluster is being created' do + let(:cluster) { create(:cluster, :providing_by_gcp) } + + it 'rejects changes' do + is_expected.to eq(false) + + expect(cluster.errors.full_messages).to include('cannot modify during creation') + end + end + end end end diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb index 361d4220c6e..ba9b7d3bdcf 100644 --- a/spec/support/shared_examples/helm_generated_script.rb +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -2,12 +2,12 @@ shared_examples 'helm commands' do describe '#generate_script' do let(:helm_setup) do <<~EOS - set -eo pipefail + set -xeo pipefail EOS end it 'should return appropriate command' do - expect(subject.generate_script).to eq(helm_setup + commands) + expect(subject.generate_script.strip).to eq((helm_setup + commands).strip) end end end diff --git a/spec/workers/cluster_platform_configure_worker_spec.rb b/spec/workers/cluster_platform_configure_worker_spec.rb index 1a7ad8923f6..b51f6e07c6a 100644 --- a/spec/workers/cluster_platform_configure_worker_spec.rb +++ b/spec/workers/cluster_platform_configure_worker_spec.rb @@ -30,4 +30,18 @@ describe ClusterPlatformConfigureWorker, '#execute' do described_class.new.perform(123) end end + + context 'when kubeclient raises error' do + let(:cluster) { create(:cluster, :project) } + + it 'rescues and logs the error' do + allow_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).to receive(:execute).and_raise(::Kubeclient::HttpError.new(500, 'something baaaad happened', '')) + + expect(Rails.logger) + .to receive(:error) + .with("Failed to create/update Kubernetes namespace for cluster_id: #{cluster.id} with error: something baaaad happened") + + described_class.new.perform(cluster.id) + end + end end |