diff options
48 files changed, 549 insertions, 137 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 91951fd8ad7..76d05362056 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.61.0 +1.62.0 diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue index 0f5c5b3d60f..72ddd8d4fcf 100644 --- a/app/assets/javascripts/monitoring/components/graph_group.vue +++ b/app/assets/javascripts/monitoring/components/graph_group.vue @@ -1,5 +1,10 @@ <script> +import Icon from '~/vue_shared/components/icon.vue'; + export default { + components: { + Icon, + }, props: { name: { type: String, @@ -15,15 +20,42 @@ export default { required: true, }, }, + data() { + return { + showGroup: true, + }; + }, + computed: { + caretIcon() { + return this.collapseGroup && this.showGroup ? 'angle-down' : 'angle-right'; + }, + }, + created() { + this.showGroup = this.collapseGroup; + }, + methods: { + collapse() { + this.showGroup = !this.showGroup; + }, + }, }; </script> <template> <div v-if="showPanels" class="card prometheus-panel"> - <div class="card-header"> - <h4>{{ name }}</h4> + <div class="card-header d-flex align-items-center"> + <h4 class="flex-grow-1">{{ name }}</h4> + <a role="button" @click="collapse"> + <icon :size="16" :aria-label="__('Toggle collapse')" :name="caretIcon" /> + </a> + </div> + <div + v-if="collapseGroup" + v-show="collapseGroup && showGroup" + class="card-body prometheus-graph-group" + > + <slot></slot> </div> - <div v-if="collapseGroup" class="card-body prometheus-graph-group"><slot></slot></div> </div> <div v-else class="prometheus-graph-group"><slot></slot></div> </template> diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 79de1d78a6e..416537ef763 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -24,12 +24,16 @@ .settings { // border-top for each item except the top one - + .settings { - border-top: 1px solid $border-color; - } + border-top: 1px solid $border-color; &:first-of-type { margin-top: 10px; + border: 0; + } + + + div .settings:first-of-type { + margin-top: 0; + border-top: 1px solid $border-color; } &.animating { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 99411641874..f2f72bea5b4 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -85,7 +85,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:import_sources]&.delete("") params[:application_setting][:restricted_visibility_levels]&.delete("") + # TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204) params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file] + params.delete(:domain_blacklist_raw) if params[:domain_blacklist] + params.delete(:domain_whitelist_raw) if params[:domain_whitelist] params.require(:application_setting).permit( visible_application_setting_attributes diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index ec8077d18e3..bcd771dafcf 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -35,6 +35,12 @@ class Clusters::ClustersController < Clusters::BaseController end def new + return unless Feature.enabled?(:create_eks_clusters) + + @gke_selected = params[:provider] == 'gke' + @eks_selected = params[:provider] == 'eks' + + return redirect_to @authorize_url if @gke_selected && @authorize_url && !@valid_gcp_token end # Overridding ActionController::Metal#status is NOT a good idea @@ -99,7 +105,7 @@ class Clusters::ClustersController < Clusters::BaseController validate_gcp_token user_cluster - render :new, locals: { active_tab: 'gcp' } + render :new, locals: { active_tab: 'create' } end end @@ -116,7 +122,7 @@ class Clusters::ClustersController < Clusters::BaseController validate_gcp_token gcp_cluster - render :new, locals: { active_tab: 'user' } + render :new, locals: { active_tab: 'add' } end end @@ -189,7 +195,8 @@ class Clusters::ClustersController < Clusters::BaseController end def generate_gcp_authorize_url - state = generate_session_key_redirect(clusterable.new_path.to_s) + params = Feature.enabled?(:create_eks_clusters) ? { provider: :gke } : {} + state = generate_session_key_redirect(clusterable.new_path(params).to_s) @authorize_url = GoogleApi::CloudPlatform::Client.new( nil, callback_google_api_auth_url, diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index e0df51590ae..c9f680a4696 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -18,10 +18,23 @@ class Projects::ServicesController < Projects::ApplicationController def update @service.attributes = service_params[:service] - if @service.save(context: :manual_change) - redirect_to(project_settings_integrations_path(@project), notice: success_message) - else - render 'edit' + saved = @service.save(context: :manual_change) + + respond_to do |format| + format.html do + if saved + redirect_to project_settings_integrations_path(@project), + notice: success_message + else + render 'edit' + end + end + + format.json do + status = saved ? :ok : :unprocessable_entity + + render json: serialize_as_json, status: status + end end end @@ -67,4 +80,10 @@ class Projects::ServicesController < Projects::ApplicationController def ensure_service_enabled render_404 unless service end + + def serialize_as_json + @service + .as_json(only: @service.json_fields) + .merge(errors: @service.errors.as_json) + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 84021d0da56..b1a6e988a1d 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -180,8 +180,12 @@ module ApplicationSettingsHelper :default_projects_limit, :default_snippet_visibility, :disabled_oauth_sign_in_sources, + :domain_blacklist, :domain_blacklist_enabled, + # TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204) :domain_blacklist_raw, + :domain_whitelist, + # TODO Remove domain_whitelist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204) :domain_whitelist_raw, :outbound_local_requests_whitelist_raw, :dsa_key_restriction, diff --git a/app/models/service.rb b/app/models/service.rb index 431c5881460..d866a51c42e 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -107,6 +107,13 @@ class Service < ApplicationRecord [] end + # Expose a list of fields in the JSON endpoint. + # + # This list is used in `Service#as_json(only: json_fields)`. + def json_fields + %w(active) + end + def test_data(project, user) Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb index d1bf0344b66..49c64b31fc7 100644 --- a/app/presenters/clusterable_presenter.rb +++ b/app/presenters/clusterable_presenter.rb @@ -25,8 +25,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated polymorphic_path([clusterable, :clusters]) end - def new_path - new_polymorphic_path([clusterable, :cluster]) + def new_path(options = {}) + new_polymorphic_path([clusterable, :cluster], options) end def create_user_clusters_path diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb index f8bbe5216f1..cce400ad2a1 100644 --- a/app/presenters/instance_clusterable_presenter.rb +++ b/app/presenters/instance_clusterable_presenter.rb @@ -18,8 +18,8 @@ class InstanceClusterablePresenter < ClusterablePresenter end override :new_path - def new_path - new_admin_cluster_path + def new_path(options = {}) + new_admin_cluster_path(options) end override :cluster_status_cluster_path diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml new file mode 100644 index 00000000000..f707c6585ec --- /dev/null +++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml @@ -0,0 +1,8 @@ +- provider = local_assigns.fetch(:provider) +- logo_path = local_assigns.fetch(:logo_path) +- label = local_assigns.fetch(:label) + += link_to clusterable.new_path(provider: provider), class: 'btn gl-button btn-outline flex-fill d-inline-flex flex-column mr-3 justify-content-center align-items-center' do + = image_tag logo_path, alt: label, class: 'gl-w-13 gl-h-13' + %span + = label diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml new file mode 100644 index 00000000000..24506205243 --- /dev/null +++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml @@ -0,0 +1,11 @@ +- gke_label = s_('ClusterIntegration|Google GKE') +- eks_label = s_('ClusterIntegration|Amazon EKS') +- create_cluster_label = s_('ClusterIntegration|Create cluster on') +.d-flex.flex-column + %h5 + = create_cluster_label + .d-flex + = render partial: 'clusters/clusters/cloud_providers/cloud_provider_button', + locals: { provider: 'gke', label: gke_label, logo_path: '' } + = render partial: 'clusters/clusters/cloud_providers/cloud_provider_button', + locals: { provider: 'eks', label: eks_label, logo_path: '' } diff --git a/app/views/clusters/clusters/eks/_index.html.haml b/app/views/clusters/clusters/eks/_index.html.haml new file mode 100644 index 00000000000..ca8e9ba527a --- /dev/null +++ b/app/views/clusters/clusters/eks/_index.html.haml @@ -0,0 +1 @@ +.js-create-eks-cluster-form-container diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml index 6a8af23e5e8..fb182d99ff0 100644 --- a/app/views/clusters/clusters/new.html.haml +++ b/app/views/clusters/clusters/new.html.haml @@ -1,6 +1,8 @@ - breadcrumb_title _('Kubernetes') - page_title _('Kubernetes Cluster') -- active_tab = local_assigns.fetch(:active_tab, 'gcp') +- create_eks_enabled = Feature.enabled?(:create_eks_clusters) +- active_tab = local_assigns.fetch(:active_tab, 'create') +- link_end = '<a/>'.html_safe = javascript_include_tag 'https://apis.google.com/js/api.js' = render_gcp_signup_offer @@ -11,26 +13,36 @@ .col-md-9.js-toggle-container %ul.nav-links.nav-tabs.gitlab-tabs.nav{ role: 'tablist' } %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#create-gcp-cluster-pane', id: 'create-gcp-cluster-tab', class: active_when(active_tab == 'gcp'), data: { toggle: 'tab' }, role: 'tab' } + %a.nav-link{ href: '#create-cluster-pane', id: 'create-cluster-tab', class: active_when(active_tab == 'create'), data: { toggle: 'tab' }, role: 'tab' } %span Create new Cluster on GKE %li.nav-item{ role: 'presentation' } - %a.nav-link{ href: '#add-user-cluster-pane', id: 'add-user-cluster-tab', class: active_when(active_tab == 'user'), data: { toggle: 'tab' }, role: 'tab' } + %a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab' }, role: 'tab' } %span Add existing cluster .tab-content.gitlab-tab-content - .tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' } - = render 'clusters/clusters/gcp/header' - - if @valid_gcp_token - = render 'clusters/clusters/gcp/form' - - elsif @authorize_url - .signin-with-google - = link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url) - = _('or') - = link_to('create a new Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer') - - else - - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') - = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } + - if create_eks_enabled + .tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } + - if @gke_selected && @valid_gcp_token + = render 'clusters/clusters/gcp/header' + = render 'clusters/clusters/gcp/form' + - elsif @eks_selected + = render 'clusters/clusters/eks/index' + - else + = render 'clusters/clusters/cloud_providers/cloud_provider_selector' + - else + .tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } + = render 'clusters/clusters/gcp/header' + - if @valid_gcp_token + = render 'clusters/clusters/gcp/form' + - elsif @authorize_url + .signin-with-google + - create_account_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral' } + = link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px', alt: _('Sign in with Google')), @authorize_url) + = s_('or %{link_start}create a new Google account%{link_end}').html_safe % { link_start: create_account_link, link_end: link_end } + - else + - documentation_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("integration/google") } + = s_('Google authentication is not %{link_start}property configured%{link_end}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_start: documentation_link_start, link_end: link_end } - .tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' } + .tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' } = render 'clusters/clusters/user/header' = render 'clusters/clusters/user/form' diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 837707707a9..3b26b8df8a1 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -41,7 +41,7 @@ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" } .value.hide-collapsed - if milestone.present? - = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport' } + = link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport', qa_selector: 'milestone_link' } - else %span.no-value = _('None') diff --git a/changelogs/unreleased/api_settings.yml b/changelogs/unreleased/api_settings.yml new file mode 100644 index 00000000000..58830a5ab97 --- /dev/null +++ b/changelogs/unreleased/api_settings.yml @@ -0,0 +1,5 @@ +--- +title: Improve application settings API +merge_request: 31149 +author: Mathieu Parent +type: fixed diff --git a/changelogs/unreleased/gitaly-version-v1.62.0.yml b/changelogs/unreleased/gitaly-version-v1.62.0.yml new file mode 100644 index 00000000000..6ae200bd7f4 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.62.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.62.0 +merge_request: 32608 +author: +type: changed diff --git a/changelogs/unreleased/issue-67127.yml b/changelogs/unreleased/issue-67127.yml new file mode 100644 index 00000000000..70848dc6ff9 --- /dev/null +++ b/changelogs/unreleased/issue-67127.yml @@ -0,0 +1,5 @@ +--- +title: Expose 'protected' field for Tag API endpoint. +merge_request: 32790 +author: Andrea Leone +type: added diff --git a/changelogs/unreleased/jivanvl-add-caret-icon-dashboard.yml b/changelogs/unreleased/jivanvl-add-caret-icon-dashboard.yml new file mode 100644 index 00000000000..148e306c3b8 --- /dev/null +++ b/changelogs/unreleased/jivanvl-add-caret-icon-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: Add caret icons to the monitoring dashboard +merge_request: 32239 +author: +type: changed diff --git a/changelogs/unreleased/pl-project-service-json.yml b/changelogs/unreleased/pl-project-service-json.yml new file mode 100644 index 00000000000..a273289d871 --- /dev/null +++ b/changelogs/unreleased/pl-project-service-json.yml @@ -0,0 +1,5 @@ +--- +title: Expose update project service endpoint JSON +merge_request: 32759 +author: +type: added diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index aa7c2d343a8..87159b695f9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -342,7 +342,7 @@ production: &base ## Sidekiq sidekiq: - log_format: default # (json is also supported) + log_format: json # (default is the original format) ## Auxiliary jobs # Periodically executed jobs, to self-heal GitLab, do external synchronizations, etc. diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index e89e9657314..f37cd518d48 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -116,3 +116,4 @@ - [incident_management, 2] - [jira_connect, 1] - [update_external_pull_requests, 3] + - [refresh_license_compliance_checks, 2] diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md index 3ee8673b297..d2d7ebdd634 100644 --- a/doc/administration/repository_storage_types.md +++ b/doc/administration/repository_storage_types.md @@ -10,7 +10,7 @@ that can be: - Mounted to the local disk - Exposed as an NFS shared volume -- Acessed via [gitaly] on its own machine. +- Accessed via [gitaly] on its own machine. In GitLab, this is configured in `/etc/gitlab/gitlab.rb` by the `git_data_dirs({})` configuration hash. The storage layouts discussed here will apply to any shard diff --git a/doc/api/settings.md b/doc/api/settings.md index a14b0d3632a..4ad4ebdacb6 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -210,7 +210,7 @@ are listed in the descriptions of the relevant settings. | `diff_max_patch_bytes` | integer | no | Maximum diff patch size (Bytes). | | `disabled_oauth_sign_in_sources` | array of strings | no | Disabled OAuth sign-in sources. | | `dns_rebinding_protection_enabled` | boolean | no | Enforce DNS rebinding attack protection. | -| `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. | +| `domain_blacklist` | array of strings | no | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. | | `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. | | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. | | `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. | diff --git a/doc/api/tags.md b/doc/api/tags.md index 1d874fea1f8..88f63d6b34b 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -46,7 +46,8 @@ Parameters: }, "name": "v1.0.0", "target": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": null + "message": null, + "protected": true } ] ``` @@ -94,7 +95,8 @@ Example Response: "committer_email": "contact@arthurverschaeve.be", "committed_date": "2015-02-01T21:56:31.000+01:00" }, - "release": null + "release": null, + "protected": false } ``` @@ -138,7 +140,8 @@ Parameters: }, "name": "v1.0.0", "target": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": null + "message": null, + "protected": false } ``` diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 557d3132d71..336ef4ab278 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -13,7 +13,7 @@ _Note:_ The action itself will not update the state, only a mutation should upda ## File structure -When using Vuex at GitLab, separate this concerns into different files to improve readability: +When using Vuex at GitLab, separate these concerns into different files to improve readability: ``` └── store diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 358ba971049..ddf2b2fb738 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -613,6 +613,9 @@ To back up GitLab: sudo gitlab-backup create ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + ### Restoring GitLab from a backup To restore GitLab, first review the [restore documentation](../../raketasks/backup_restore.md#restore), @@ -631,6 +634,9 @@ released, you can update your GitLab instance: sudo gitlab-backup create ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + 1. Update the repositories and install GitLab: ```sh diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md index 50e491f29a2..4a96001f2de 100644 --- a/doc/migrate_ci_to_ce/README.md +++ b/doc/migrate_ci_to_ce/README.md @@ -77,6 +77,9 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production SKIP=r If this fails you need to fix it before upgrading to 8.0. Also see <https://about.gitlab.com/get-help/> +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + ### 2. Check source and target database types Check what databases you use on your GitLab server and your CI server. diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 06048ad618f..c230bb413f2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -80,6 +80,9 @@ Use this command if you've installed GitLab with the Omnibus package: sudo gitlab-backup create ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + Use this if you've installed GitLab from source: ```sh @@ -92,6 +95,9 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t <container name> gitlab-backup create ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + If you are using the [GitLab helm chart](https://gitlab.com/gitlab-org/charts/gitlab) on a Kubernetes cluster, you can run the backup task using `backup-utility` script on the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details: @@ -202,6 +208,9 @@ To use the `copy` strategy instead of the default streaming strategy, specify sudo gitlab-backup create STRATEGY=copy ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + ### Backup filename By default a backup file is created according to the specification in [the Backup timestamp](#backup-timestamp) section above. You can however override the `[TIMESTAMP]` part of the filename by setting the `BACKUP` environment variable. For example: @@ -210,6 +219,9 @@ By default a backup file is created according to the specification in [the Backu sudo gitlab-backup create BACKUP=dump ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + The resulting file will then be `dump_gitlab_backup.tar`. This is useful for systems that make use of rsync and incremental backups, and will result in considerably faster transfer speeds. ### Rsyncable @@ -222,6 +234,9 @@ Note that the `--rsyncable` option in `gzip` is not guaranteed to be available o sudo gitlab-backup create BACKUP=dump GZIP_RSYNCABLE=yes ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + ### Excluding specific directories from the backup You can choose what should be exempt from the backup up by adding the environment variable `SKIP`. @@ -247,6 +262,9 @@ For Omnibus GitLab packages: sudo gitlab-backup create SKIP=db,uploads ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + For installations from source: ```sh @@ -452,6 +470,9 @@ sudo gitlab-backup create DIRECTORY=daily sudo gitlab-backup create DIRECTORY=weekly ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + ### Uploading to locally mounted shares You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by @@ -569,6 +590,9 @@ There, add the following line to schedule the backup for everyday at 2 AM: 0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1 ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:create`. + You may also want to set a limited lifetime for backups to prevent regular backups using all your disk space. @@ -729,6 +753,14 @@ restore: sudo gitlab-backup restore BACKUP=1493107454_2018_04_25_10.6.4-ce ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:restore`. + +CAUTION: **Warning:** +`gitlab-rake gitlab:backup:restore` does not set the right file system permissions on your Registry directory. +This is a [known issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/62759). On GitLab 12.2 or newer, you can +use `gitlab-backup restore` to avoid this issue. + Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above. Reconfigure, restart and check GitLab: @@ -763,6 +795,14 @@ For docker installations, the restore task can be run from host: docker exec -it <name of container> gitlab-backup restore ``` +NOTE: **Note** +For GitLab 12.1 and earlier, use `gitlab-rake gitlab:backup:restore`. + +CAUTION: **Warning:** +`gitlab-rake gitlab:backup:restore` does not set the right file system permissions on your Registry directory. +This is a [known issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/62759). On GitLab 12.2 or newer, you can +use `gitlab-backup restore` to avoid this issue. + The GitLab helm chart uses a different process, documented in [restoring a GitLab helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md). @@ -978,7 +1018,7 @@ sudo chown -R registry:registry /var/opt/gitlab/gitlab-rails/shared/registry/doc NOTE: **Note:** If you have changed the default filesystem location for the registry, you will -want to run the chown against your custom location instead of +want to run the `chown` against your custom location instead of `/var/opt/gitlab/gitlab-rails/shared/registry/docker`. [reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure @@ -990,6 +1030,7 @@ While running the backup, you may receive a gzip error: ```sh sudo /opt/gitlab/bin/gitlab-backup create +... Dumping ... ... gzip: stdout: Input/output error @@ -999,5 +1040,5 @@ Backup failed If this happens, check the following: -1. Confirm there is sufficent diskspace for the gzip operation. -1. If NFS is being used, check if the mount option `timeo` is set. The default is `600`, and changing this to smaller values have resulted in this error. +1. Confirm there is sufficient disk space for the gzip operation. +1. If NFS is being used, check if the mount option `timeout` is set. The default is `600`, and changing this to smaller values have resulted in this error. diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 89526c08e7e..d7b2572c717 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -146,7 +146,7 @@ Dependency Scanning can be [configured](#customizing-the-dependency-scanning-set using environment variables. | Environment variable | Description | Example usage | -|-------------------------------- |-------------| | +| --------------------------------------- | ----------- | ------------- | | `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). | | | `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). | | | `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). | | diff --git a/doc/user/project/canary_deployments.md b/doc/user/project/canary_deployments.md index 5068d2757be..d4c8bf0d309 100644 --- a/doc/user/project/canary_deployments.md +++ b/doc/user/project/canary_deployments.md @@ -2,7 +2,7 @@ > [Introduced][ee-1659] in [GitLab Premium][eep] 9.1. -A popular [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration) +A popular [Continuous Deployment](https://en.wikipedia.org/wiki/Continuous_deployment) strategy, where a small portion of the fleet is updated to the new version of your application. diff --git a/lib/api/entities.rb b/lib/api/entities.rb index c9b3483acaf..312c8d5b548 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1312,6 +1312,10 @@ module API options[:project].releases.find_by(tag: repo_tag.name) end # rubocop: enable CodeReuse/ActiveRecord + + expose :protected do |repo_tag, options| + ::ProtectedTag.protected?(options[:project], repo_tag.name) + end end class Runner < Grape::Entity diff --git a/lib/api/settings.rb b/lib/api/settings.rb index dd27ebab83d..acf03051a5b 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -50,10 +50,8 @@ module API optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources' optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups' - given domain_blacklist_enabled: ->(val) { val } do - requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' - end - optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + optional :domain_blacklist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + optional :domain_whitelist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.' optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.' optional :gitaly_timeout_default, type: Integer, desc: 'Default Gitaly timeout, in seconds. Set to 0 to disable timeouts.' @@ -74,7 +72,7 @@ module API requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run." end optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.' - optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest], + optional :import_sources, type: Array[String], values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts" optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a77d70a5700..32deab7dd68 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2530,6 +2530,9 @@ msgstr "" msgid "ClusterIntegration|Alternatively" msgstr "" +msgid "ClusterIntegration|Amazon EKS" +msgstr "" + msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later." msgstr "" @@ -2608,6 +2611,9 @@ msgstr "" msgid "ClusterIntegration|Create Kubernetes cluster" msgstr "" +msgid "ClusterIntegration|Create cluster on" +msgstr "" + msgid "ClusterIntegration|Did you know?" msgstr "" @@ -2659,6 +2665,9 @@ msgstr "" msgid "ClusterIntegration|Google Cloud Platform project" msgstr "" +msgid "ClusterIntegration|Google GKE" +msgstr "" + msgid "ClusterIntegration|Google Kubernetes Engine" msgstr "" @@ -2989,9 +2998,6 @@ msgstr "" msgid "ClusterIntegration|pricing" msgstr "" -msgid "ClusterIntegration|properly configured" -msgstr "" - msgid "ClusterIntegration|sign up" msgstr "" @@ -5518,7 +5524,7 @@ msgstr "" msgid "Google Takeout" msgstr "" -msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." +msgid "Google authentication is not %{link_start}property configured%{link_end}. Ask your GitLab administrator if you want to use this service." msgstr "" msgid "Got it" @@ -10565,6 +10571,9 @@ msgstr "" msgid "Sign in via 2FA code" msgstr "" +msgid "Sign in with Google" +msgstr "" + msgid "Sign out" msgstr "" @@ -12299,6 +12308,9 @@ msgstr "" msgid "Toggle backtrace" msgstr "" +msgid "Toggle collapse" +msgstr "" + msgid "Toggle comments for this file" msgstr "" @@ -14180,7 +14192,7 @@ msgstr "" msgid "nounSeries|%{item}, and %{lastItem}" msgstr "" -msgid "or" +msgid "or %{link_start}create a new Google account%{link_end}" msgstr "" msgid "out of %d total test" diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 52929ece9ed..e5e26b1864b 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -35,12 +35,17 @@ module QA element :labels_block element :edit_link_labels element :dropdown_menu_labels + element :milestone_link end view 'app/views/shared/issuable/_close_reopen_button.html.haml' do element :reopen_issue_button end + def click_milestone_link + click_element(:milestone_link) + end + # Adds a comment to an issue # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) diff --git a/qa/qa/page/project/milestone/index.rb b/qa/qa/page/project/milestone/index.rb index 6895c44f72f..8ad7689ce70 100644 --- a/qa/qa/page/project/milestone/index.rb +++ b/qa/qa/page/project/milestone/index.rb @@ -17,3 +17,5 @@ module QA end end end + +QA::Page::Project::Milestone::Index.prepend_if_ee('QA::EE::Page::Project::Milestone::Index') diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index 16ab59352f3..8539eaeb337 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -3,7 +3,7 @@ module QA module Resource class Issue < Base - attr_writer :description, :milestone + attr_writer :description, :milestone, :weight attribute :project do Project.fabricate! do |resource| @@ -46,6 +46,7 @@ module QA title: title }.tap do |hash| hash[:milestone_id] = @milestone.id if @milestone + hash[:weight] = @weight if @weight end end end diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index e5501535875..afc059d7561 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -73,8 +73,8 @@ describe Admin::ClustersController do end describe 'GET #new' do - def get_new - get :new + def get_new(provider: 'gke') + get :new, params: { provider: provider } end describe 'functionality for new cluster' do @@ -85,6 +85,7 @@ describe Admin::ClustersController do end before do + stub_feature_flags(create_eks_clusters: false) allow(SecureRandom).to receive(:hex).and_return(key) end @@ -94,6 +95,20 @@ describe Admin::ClustersController do expect(assigns(:authorize_url)).to include(key) expect(session[session_key_for_redirect_uri]).to eq(new_admin_cluster_path) end + + context 'when create_eks_clusters feature flag is enabled' do + before do + stub_feature_flags(create_eks_clusters: true) + end + + context 'when selected provider is gke and no valid gcp token exists' do + it 'redirects to gcp authorize_url' do + get_new + + expect(response).to redirect_to(assigns(:authorize_url)) + end + end + end end context 'when omniauth has not configured' do diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 09677b42887..5a3ba51d4df 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -85,8 +85,8 @@ describe Groups::ClustersController do end describe 'GET new' do - def go - get :new, params: { group_id: group } + def go(provider: 'gke') + get :new, params: { group_id: group, provider: provider } end describe 'functionality for new cluster' do @@ -97,6 +97,7 @@ describe Groups::ClustersController do end before do + stub_feature_flags(create_eks_clusters: false) allow(SecureRandom).to receive(:hex).and_return(key) end @@ -106,6 +107,20 @@ describe Groups::ClustersController do expect(assigns(:authorize_url)).to include(key) expect(session[session_key_for_redirect_uri]).to eq(new_group_cluster_path(group)) end + + context 'when create_eks_clusters feature flag is enabled' do + before do + stub_feature_flags(create_eks_clusters: true) + end + + context 'when selected provider is gke and no valid gcp token exists' do + it 'redirects to gcp authorize_url' do + go + + expect(response).to redirect_to(assigns(:authorize_url)) + end + end + end end context 'when omniauth has not configured' do diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 35cbab57037..8ac72df5d20 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -79,8 +79,12 @@ describe Projects::ClustersController do end describe 'GET new' do - def go - get :new, params: { namespace_id: project.namespace, project_id: project } + def go(provider: 'gke') + get :new, params: { + namespace_id: project.namespace, + project_id: project, + provider: provider + } end describe 'functionality for new cluster' do @@ -91,6 +95,7 @@ describe Projects::ClustersController do end before do + stub_feature_flags(create_eks_clusters: false) allow(SecureRandom).to receive(:hex).and_return(key) end @@ -100,6 +105,20 @@ describe Projects::ClustersController do expect(assigns(:authorize_url)).to include(key) expect(session[session_key_for_redirect_uri]).to eq(new_project_cluster_path(project)) end + + context 'when create_eks_clusters feature flag is enabled' do + before do + stub_feature_flags(create_eks_clusters: true) + end + + context 'when selected provider is gke and no valid gcp token exists' do + it 'redirects to gcp authorize_url' do + go + + expect(response).to redirect_to(assigns(:authorize_url)) + end + end + end end context 'when omniauth has not configured' do diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 180d997a8e8..0c074714bf3 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -19,9 +19,9 @@ describe Projects::ServicesController do it 'renders 404' do allow_any_instance_of(Service).to receive(:can_test?).and_return(false) - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param } + put :test, params: project_params - expect(response).to have_gitlab_http_status(404) + expect(response).to have_gitlab_http_status(:not_found) end end @@ -29,11 +29,11 @@ describe Projects::ServicesController do let(:service_params) { { active: 'true', url: '' } } it 'returns error messages in JSON response' do - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } + put :test, params: project_params(service: service_params) - expect(json_response['message']).to eq "Validations failed." + expect(json_response['message']).to eq 'Validations failed.' expect(json_response['service_response']).to include "Url can't be blank" - expect(response).to have_gitlab_http_status(200) + expect(response).to be_successful end end @@ -47,9 +47,9 @@ describe Projects::ServicesController do it 'returns success' do allow_any_instance_of(MicrosoftTeams::Notifier).to receive(:ping).and_return(true) - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param } + put :test, params: project_params - expect(response.status).to eq(200) + expect(response).to be_successful end end @@ -57,11 +57,11 @@ describe Projects::ServicesController do stub_request(:get, 'http://example.com/rest/api/2/serverInfo') .to_return(status: 200, body: '{}') - expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original + expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } + put :test, params: project_params(service: service_params) - expect(response.status).to eq(200) + expect(response).to be_successful end end @@ -69,14 +69,23 @@ describe Projects::ServicesController do stub_request(:get, 'http://example.com/rest/api/2/serverInfo') .to_return(status: 200, body: '{}') - expect(Gitlab::HTTP).to receive(:get).with("/rest/api/2/serverInfo", any_args).and_call_original + expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } + put :test, params: project_params(service: service_params) - expect(response.status).to eq(200) + expect(response).to be_successful end context 'when service is configured for the first time' do + let(:service_params) do + { + 'active' => '1', + 'push_events' => '1', + 'token' => 'token', + 'project_url' => 'http://test.com' + } + end + before do allow_any_instance_of(ServiceHook).to receive(:execute).and_return(true) end @@ -84,7 +93,7 @@ describe Projects::ServicesController do it 'persist the object' do do_put - expect(response).to have_gitlab_http_status(200) + expect(response).to be_successful expect(json_response).to be_empty expect(BuildkiteService.first).to be_present end @@ -92,18 +101,14 @@ describe Projects::ServicesController do it 'creates the ServiceHook object' do do_put - expect(response).to have_gitlab_http_status(200) + expect(response).to be_successful expect(json_response).to be_empty expect(BuildkiteService.first.service_hook).to be_present end def do_put - put :test, params: { - namespace_id: project.namespace, - project_id: project, - id: 'buildkite', - service: { 'active' => '1', 'push_events' => '1', token: 'token', 'project_url' => 'http://test.com' } - } + put :test, params: project_params(id: 'buildkite', + service: service_params) end end end @@ -113,9 +118,9 @@ describe Projects::ServicesController do stub_request(:get, 'http://example.com/rest/api/2/serverInfo') .to_return(status: 404) - put :test, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: service_params } + put :test, params: project_params(service: service_params) - expect(response).to have_gitlab_http_status(200) + expect(response).to be_successful expect(json_response).to eq( 'error' => true, 'message' => 'Test failed.', @@ -127,39 +132,70 @@ describe Projects::ServicesController do end describe 'PUT #update' do - context 'when param `active` is set to true' do - it 'activates the service and redirects to integrations paths' do - put :update, - params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } + describe 'as HTML' do + let(:service_params) { { active: true } } - expect(response).to redirect_to(project_settings_integrations_path(project)) - expect(flash[:notice]).to eq 'Jira activated.' + before do + put :update, params: project_params(service: service_params) + end + + context 'when param `active` is set to true' do + it 'activates the service and redirects to integrations paths' do + expect(response).to redirect_to(project_settings_integrations_path(project)) + expect(flash[:notice]).to eq 'Jira activated.' + end + end + + context 'when param `active` is set to false' do + let(:service_params) { { active: false } } + + it 'does not activate the service but saves the settings' do + expect(flash[:notice]).to eq 'Jira settings saved, but not activated.' + end end - end - context 'when param `active` is set to false' do - it 'does not activate the service but saves the settings' do - put :update, - params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } } + context 'when activating Jira service from a template' do + let(:service) do + create(:jira_service, project: project, template: true) + end - expect(flash[:notice]).to eq 'Jira settings saved, but not activated.' + it 'activate Jira service from template' do + expect(flash[:notice]).to eq 'Jira activated.' + end end end - context 'when activating Jira service from a template' do - let(:template_service) { create(:jira_service, project: project, template: true) } + describe 'as JSON' do + before do + put :update, params: project_params(service: service_params, format: :json) + end + + context 'when update succeeds' do + let(:service_params) { { url: 'http://example.com' } } + + it 'returns JSON response with no errors' do + expect(response).to be_successful + expect(json_response).to include('active' => true, 'errors' => {}) + end + end - it 'activate Jira service from template' do - put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } + context 'when update fails' do + let(:service_params) { { url: '' } } - expect(flash[:notice]).to eq 'Jira activated.' + it 'returns JSON response with errors' do + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response).to include( + 'active' => true, + 'errors' => { 'url' => ['must be a valid URL', %{can't be blank}] } + ) + end end end end - describe "GET #edit" do + describe 'GET #edit' do before do - get :edit, params: { namespace_id: project.namespace, project_id: project, id: 'jira' } + get :edit, params: project_params(id: 'jira') end context 'with approved services' do @@ -168,4 +204,14 @@ describe Projects::ServicesController do end end end + + private + + def project_params(opts = {}) + opts.reverse_merge( + namespace_id: project.namespace, + project_id: project, + id: service.to_param + ) + end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 820ce48e52c..a11237db508 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -18,6 +18,8 @@ describe 'Gcp Cluster', :js do let(:project_id) { 'test-project-1234' } before do + stub_feature_flags(create_eks_clusters: false) + allow_any_instance_of(Projects::ClustersController) .to receive(:token_in_session).and_return('token') allow_any_instance_of(Projects::ClustersController) @@ -147,6 +149,7 @@ describe 'Gcp Cluster', :js do context 'when user has not signed with Google' do before do + stub_feature_flags(create_eks_clusters: false) visit project_clusters_path(project) click_link 'Add Kubernetes cluster' diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index ce382c19fc1..d1cd19dff2d 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -51,6 +51,7 @@ describe 'Clusters', :js do context 'when user has not signed in Google' do before do + stub_feature_flags(create_eks_clusters: false) visit project_clusters_path(project) click_link 'Add Kubernetes cluster' @@ -62,4 +63,27 @@ describe 'Clusters', :js do expect(page).to have_link('Google account') end end + + context 'when create_eks_clusters feature flag is enabled' do + before do + stub_feature_flags(create_eks_clusters: true) + end + + context 'when user access create cluster page' do + before do + visit project_clusters_path(project) + + click_link 'Add Kubernetes cluster' + click_link 'Create new Cluster on GKE' + end + + it 'user sees a link to create a GKE cluster' do + expect(page).to have_link('Google GKE') + end + + it 'user sees a link to create an EKS cluster' do + expect(page).to have_link('Amazon EKS') + end + end + end end diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json index 5713ea1f526..bb0190955f0 100644 --- a/spec/fixtures/api/schemas/public_api/v4/tag.json +++ b/spec/fixtures/api/schemas/public_api/v4/tag.json @@ -16,7 +16,8 @@ { "type": "null" }, { "$ref": "release/tag_release.json" } ] - } + }, + "protected": { "type": "boolean" } }, "additionalProperties": false } diff --git a/spec/javascripts/monitoring/components/graph_group_spec.js b/spec/javascripts/monitoring/components/graph_group_spec.js new file mode 100644 index 00000000000..068c4b5302c --- /dev/null +++ b/spec/javascripts/monitoring/components/graph_group_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import GraphGroup from '~/monitoring/components/graph_group.vue'; + +describe('Graph group component', () => { + let graphGroup; + + afterEach(() => { + graphGroup.destroy(); + }); + + describe('When groups can be collapsed', () => { + beforeEach(() => { + graphGroup = shallowMount(GraphGroup, { + propsData: { + name: 'panel', + collapseGroup: true, + }, + }); + }); + + it('should show the angle-down caret icon when collapseGroup is true', () => { + expect(graphGroup.vm.caretIcon).toBe('angle-down'); + }); + + it('should show the angle-right caret icon when collapseGroup is false', () => { + graphGroup.vm.collapse(); + + expect(graphGroup.vm.caretIcon).toBe('angle-right'); + }); + }); + + describe('When groups can not be collapsed', () => { + beforeEach(() => { + graphGroup = shallowMount(GraphGroup, { + propsData: { + name: 'panel', + collapseGroup: true, + showPanels: false, + }, + }); + }); + + it('should not contain a prometheus-graph-group container when showPanels is false', () => { + expect(graphGroup.vm.$el.querySelector('.prometheus-graph-group')).toBe(null); + }); + }); +}); diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index dafa4243145..e496ab4cd35 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -376,6 +376,7 @@ project: - index_status - feature_usage - approval_rules +- approval_merge_request_rules - approvers - approver_users - pages_domains diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 048d04cdefd..d98b9be726a 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -252,5 +252,43 @@ describe API::Settings, 'Settings' do expect(json_response['asset_proxy_whitelist']).to eq(['example.com', '*.example.com', 'localhost']) end end + + context 'domain_blacklist settings' do + it 'rejects domain_blacklist_enabled when domain_blacklist is empty' do + put api('/application/settings', admin), + params: { + domain_blacklist_enabled: true, + domain_blacklist: [] + } + + expect(response).to have_gitlab_http_status(400) + message = json_response["message"] + expect(message["domain_blacklist"]).to eq(["Domain blacklist cannot be empty if Blacklist is enabled."]) + end + + it 'allows array for domain_blacklist' do + put api('/application/settings', admin), + params: { + domain_blacklist_enabled: true, + domain_blacklist: ['domain1.com', 'domain2.com'] + } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['domain_blacklist_enabled']).to be(true) + expect(json_response['domain_blacklist']).to eq(['domain1.com', 'domain2.com']) + end + + it 'allows a string for domain_blacklist' do + put api('/application/settings', admin), + params: { + domain_blacklist_enabled: true, + domain_blacklist: 'domain3.com, *.domain4.com' + } + + expect(response).to have_gitlab_http_status(200) + expect(json_response['domain_blacklist_enabled']).to be(true) + expect(json_response['domain_blacklist']).to eq(['domain3.com', '*.domain4.com']) + end + end end end diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb index 76d82649c5f..f2f31e1b7f2 100644 --- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true shared_examples_for 'multiple issue boards' do - dropdown_selector = '.js-boards-selector .dropdown-menu' - context 'authorized user' do before do parent.add_maintainer(user) @@ -20,18 +18,14 @@ shared_examples_for 'multiple issue boards' do end it 'shows a list of boards' do - click_button board.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do expect(page).to have_content(board.name) expect(page).to have_content(board2.name) end end it 'switches current board' do - click_button board.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do click_link board2.name end @@ -43,9 +37,7 @@ shared_examples_for 'multiple issue boards' do end it 'creates new board without detailed configuration' do - click_button board.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do click_button 'Create new board' end @@ -57,28 +49,23 @@ shared_examples_for 'multiple issue boards' do end it 'deletes board' do - click_button board.name - - wait_for_requests - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do click_button 'Delete board' end expect(page).to have_content('Are you sure you want to delete this board?') click_button 'Delete' - click_button board2.name - page.within(dropdown_selector) do + wait_for_requests + + in_boards_switcher_dropdown do expect(page).not_to have_content(board.name) expect(page).to have_content(board2.name) end end it 'adds a list to the none default board' do - click_button board.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do click_link board2.name end @@ -100,9 +87,7 @@ shared_examples_for 'multiple issue boards' do expect(page).to have_selector('.board', count: 3) - click_button board2.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do click_link board.name end @@ -114,9 +99,9 @@ shared_examples_for 'multiple issue boards' do it 'maintains sidebar state over board switch' do assert_boards_nav_active - find('.boards-switcher').click - wait_for_requests - click_link board2.name + in_boards_switcher_dropdown do + click_link board2.name + end assert_boards_nav_active end @@ -129,15 +114,24 @@ shared_examples_for 'multiple issue boards' do end it 'does not show action links' do - click_button board.name - - page.within(dropdown_selector) do + in_boards_switcher_dropdown do expect(page).not_to have_content('Create new board') expect(page).not_to have_content('Delete board') end end end + def in_boards_switcher_dropdown + find('.boards-switcher').click + + wait_for_requests + + dropdown_selector = '.js-boards-selector .dropdown-menu' + page.within(dropdown_selector) do + yield + end + end + def assert_boards_nav_active expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards') end |