Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue84
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue104
-rw-r--r--app/graphql/gitlab_schema.rb56
-rw-r--r--app/models/clusters/applications/jupyter.rb2
-rw-r--r--app/models/project_services/external_wiki_service.rb15
-rw-r--r--changelogs/unreleased/15398-expiration-policies-update-api.yml5
-rw-r--r--changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml5
-rw-r--r--changelogs/unreleased/37725-test-reports-smart-list.yml5
-rw-r--r--changelogs/unreleased/37963-document-var-default.yml5
-rw-r--r--changelogs/unreleased/update-jupyterhub-chart.yml5
-rw-r--r--doc/administration/gitaly/praefect.md89
-rw-r--r--doc/administration/packages/container_registry.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql70
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json204
-rw-r--r--doc/api/graphql/reference/index.md18
-rw-r--r--doc/api/projects.md10
-rw-r--r--doc/ci/docker/using_docker_images.md26
-rw-r--r--doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md6
-rw-r--r--doc/ci/yaml/README.md8
-rw-r--r--doc/development/performance.md2
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/project/pages/getting_started_part_four.md18
-rw-r--r--doc/user/project/pages/introduction.md4
-rw-r--r--lib/api/entities.rb12
-rw-r--r--lib/api/helpers/projects_helpers.rb12
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml1
-rw-r--r--lib/gitlab/database_importers/self_monitoring/project/delete_service.rb60
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/graphql/gitlab_schema_spec.rb16
-rw-r--r--spec/graphql/types/query_type_spec.rb11
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb57
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb5
-rw-r--r--spec/models/cycle_analytics/code_spec.rb12
-rw-r--r--spec/models/cycle_analytics/group_level_spec.rb10
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb13
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb13
-rw-r--r--spec/models/cycle_analytics/production_spec.rb19
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb10
-rw-r--r--spec/models/cycle_analytics/review_spec.rb6
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb18
-rw-r--r--spec/models/cycle_analytics/test_spec.rb26
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb30
-rw-r--r--spec/requests/api/projects_spec.rb16
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb4
-rw-r--r--spec/support/helpers/graphql_helpers.rb24
-rw-r--r--spec/support/matchers/graphql_matchers.rb18
-rw-r--r--spec/support/shared_examples/graphql/failure_to_find_anything.rb17
48 files changed, 907 insertions, 259 deletions
diff --git a/.gitignore b/.gitignore
index 7e038bff3b3..0513bcf19cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,4 +86,4 @@ jsdoc/
.projections.json
/qa/.rakeTasks
webpack-dev-server.json
-.nvimrc
+/.nvimrc
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
index 28b2c706320..65c1f125b55 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -3,11 +3,13 @@ import { mapGetters } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import store from '~/pipelines/stores/test_reports';
import { __ } from '~/locale';
+import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
name: 'TestsSuiteTable',
components: {
Icon,
+ SmartVirtualList,
},
store,
props: {
@@ -23,6 +25,8 @@ export default {
return this.getSuiteTests.length > 0;
},
},
+ maxShownRows: 30,
+ typicalRowHeight: 75,
};
</script>
@@ -34,7 +38,7 @@ export default {
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table js-test-cases-table">
+ <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
<div role="rowheader" class="table-section section-20">
{{ __('Class') }}
@@ -53,52 +57,58 @@ export default {
</div>
</div>
- <div
- v-for="(testCase, index) in getSuiteTests"
- :key="index"
- class="gl-responsive-table-row rounded align-items-md-start mt-sm-3 js-case-row"
+ <smart-virtual-list
+ :length="getSuiteTests.length"
+ :remain="$options.maxShownRows"
+ :size="$options.typicalRowHeight"
>
- <div class="table-section section-20 section-wrap">
- <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
- <div class="table-mobile-content pr-md-1">{{ testCase.classname }}</div>
- </div>
+ <div
+ v-for="(testCase, index) in getSuiteTests"
+ :key="index"
+ class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
+ >
+ <div class="table-section section-20 section-wrap">
+ <div role="rowheader" class="table-mobile-header">{{ __('Class') }}</div>
+ <div class="table-mobile-content pr-md-1 text-truncate">{{ testCase.classname }}</div>
+ </div>
- <div class="table-section section-20 section-wrap">
- <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
- <div class="table-mobile-content">{{ testCase.name }}</div>
- </div>
+ <div class="table-section section-20 section-wrap">
+ <div role="rowheader" class="table-mobile-header">{{ __('Name') }}</div>
+ <div class="table-mobile-content">{{ testCase.name }}</div>
+ </div>
- <div class="table-section section-10 section-wrap">
- <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
- <div class="table-mobile-content text-center">
- <div
- class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
- :class="`ci-status-icon-${testCase.status}`"
- >
- <icon :size="24" :name="testCase.icon" />
+ <div class="table-section section-10 section-wrap">
+ <div role="rowheader" class="table-mobile-header">{{ __('Status') }}</div>
+ <div class="table-mobile-content text-center">
+ <div
+ class="add-border ci-status-icon d-flex align-items-center justify-content-end justify-content-md-center"
+ :class="`ci-status-icon-${testCase.status}`"
+ >
+ <icon :size="24" :name="testCase.icon" />
+ </div>
</div>
</div>
- </div>
- <div class="table-section flex-grow-1">
- <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
- <div class="table-mobile-content">
- <pre
- v-if="testCase.system_output"
- class="build-trace build-trace-rounded text-left"
- ><code class="bash p-0">{{testCase.system_output}}</code></pre>
+ <div class="table-section flex-grow-1">
+ <div role="rowheader" class="table-mobile-header">{{ __('Trace'), }}</div>
+ <div class="table-mobile-content">
+ <pre
+ v-if="testCase.system_output"
+ class="build-trace build-trace-rounded text-left"
+ ><code class="bash p-0">{{testCase.system_output}}</code></pre>
+ </div>
</div>
- </div>
- <div class="table-section section-10 section-wrap">
- <div role="rowheader" class="table-mobile-header">
- {{ __('Duration') }}
- </div>
- <div class="table-mobile-content text-right">
- {{ testCase.formattedTime }}
+ <div class="table-section section-10 section-wrap">
+ <div role="rowheader" class="table-mobile-header">
+ {{ __('Duration') }}
+ </div>
+ <div class="table-mobile-content text-right pr-sm-1">
+ {{ testCase.formattedTime }}
+ </div>
</div>
</div>
- </div>
+ </smart-virtual-list>
</div>
<div v-else>
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
index 96177512e35..6effd6e949d 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_summary_table.vue
@@ -2,9 +2,13 @@
import { mapGetters } from 'vuex';
import { s__ } from '~/locale';
import store from '~/pipelines/stores/test_reports';
+import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
export default {
name: 'TestsSummaryTable',
+ components: {
+ SmartVirtualList,
+ },
store,
props: {
heading: {
@@ -24,6 +28,8 @@ export default {
this.$emit('row-click', suite);
},
},
+ maxShownRows: 20,
+ typicalRowHeight: 55,
};
</script>
@@ -35,7 +41,7 @@ export default {
</div>
</div>
- <div v-if="hasSuites" class="test-reports-table js-test-suites-table">
+ <div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table">
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
<div role="rowheader" class="table-section section-25 pl-3">
{{ __('Suite') }}
@@ -60,66 +66,72 @@ export default {
</div>
</div>
- <div
- v-for="(testSuite, index) in getTestSuites"
- :key="index"
- role="row"
- class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row"
- @click="tableRowClick(testSuite)"
+ <smart-virtual-list
+ :length="getTestSuites.length"
+ :remain="$options.maxShownRows"
+ :size="$options.typicalRowHeight"
>
- <div class="table-section section-25">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Suite') }}
- </div>
- <div class="table-mobile-content underline cgray pl-3">
- {{ testSuite.name }}
+ <div
+ v-for="(testSuite, index) in getTestSuites"
+ :key="index"
+ role="row"
+ class="gl-responsive-table-row gl-responsive-table-row-clickable test-reports-summary-row rounded cursor-pointer js-suite-row"
+ @click="tableRowClick(testSuite)"
+ >
+ <div class="table-section section-25">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Suite') }}
+ </div>
+ <div class="table-mobile-content underline cgray pl-3">
+ {{ testSuite.name }}
+ </div>
</div>
- </div>
- <div class="table-section section-25">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Duration') }}
+ <div class="table-section section-25">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Duration') }}
+ </div>
+ <div class="table-mobile-content text-md-left">
+ {{ testSuite.formattedTime }}
+ </div>
</div>
- <div class="table-mobile-content text-md-left">
- {{ testSuite.formattedTime }}
- </div>
- </div>
- <div class="table-section section-10 text-center">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Failed') }}
+ <div class="table-section section-10 text-center">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Failed') }}
+ </div>
+ <div class="table-mobile-content">{{ testSuite.failed_count }}</div>
</div>
- <div class="table-mobile-content">{{ testSuite.failed_count }}</div>
- </div>
- <div class="table-section section-10 text-center">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Errors') }}
+ <div class="table-section section-10 text-center">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Errors') }}
+ </div>
+ <div class="table-mobile-content">{{ testSuite.error_count }}</div>
</div>
- <div class="table-mobile-content">{{ testSuite.error_count }}</div>
- </div>
- <div class="table-section section-10 text-center">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Skipped') }}
+ <div class="table-section section-10 text-center">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Skipped') }}
+ </div>
+ <div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
</div>
- <div class="table-mobile-content">{{ testSuite.skipped_count }}</div>
- </div>
- <div class="table-section section-10 text-center">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Passed') }}
+ <div class="table-section section-10 text-center">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Passed') }}
+ </div>
+ <div class="table-mobile-content">{{ testSuite.success_count }}</div>
</div>
- <div class="table-mobile-content">{{ testSuite.success_count }}</div>
- </div>
- <div class="table-section section-10 text-right pr-md-3">
- <div role="rowheader" class="table-mobile-header font-weight-bold">
- {{ __('Total') }}
+ <div class="table-section section-10 text-right pr-md-3">
+ <div role="rowheader" class="table-mobile-header font-weight-bold">
+ {{ __('Total') }}
+ </div>
+ <div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div>
- <div class="table-mobile-content">{{ testSuite.total_count }}</div>
</div>
- </div>
+ </smart-virtual-list>
</div>
<div v-else>
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index ccbb1d56030..ea5776534d5 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -57,13 +57,55 @@ class GitlabSchema < GraphQL::Schema
object.to_global_id
end
+ # Find an object by looking it up from its global ID, passed as a string.
+ #
+ # This is the composition of 'parse_gid' and 'find_by_gid', see these
+ # methods for further documentation.
def object_from_id(global_id, ctx = {})
+ gid = parse_gid(global_id, ctx)
+
+ find_by_gid(gid)
+ end
+
+ # Find an object by looking it up from its 'GlobalID'.
+ #
+ # * For `ApplicationRecord`s, this is equivalent to
+ # `global_id.model_class.find(gid.model_id)`, but more efficient.
+ # * For classes that implement `.lazy_find(global_id)`, this class method
+ # will be called.
+ # * All other classes will use `GlobalID#find`
+ def find_by_gid(gid)
+ if gid.model_class < ApplicationRecord
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
+ elsif gid.model_class.respond_to?(:lazy_find)
+ gid.model_class.lazy_find(gid.model_id)
+ else
+ gid.find
+ end
+ end
+
+ # Parse a string to a GlobalID, raising ArgumentError if there are problems
+ # with it.
+ #
+ # Problems that may occur:
+ # * it may not be syntactically valid
+ # * it may not match the expected type (see below)
+ #
+ # Options:
+ # * :expected_type [Class] - the type of object this GlobalID should refer to.
+ #
+ # e.g.
+ #
+ # ```
+ # gid = GitlabSchema.parse_gid(my_string, expected_type: ::Project)
+ # project_id = gid.model_id
+ # gid.model_class == ::Project
+ # ```
+ def parse_gid(global_id, ctx = {})
expected_type = ctx[:expected_type]
gid = GlobalID.parse(global_id)
- unless gid
- raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id."
- end
+ raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." unless gid
if expected_type && !gid.model_class.ancestors.include?(expected_type)
vars = { global_id: global_id, expected_type: expected_type }
@@ -71,13 +113,7 @@ class GitlabSchema < GraphQL::Schema
raise Gitlab::Graphql::Errors::ArgumentError, msg
end
- if gid.model_class < ApplicationRecord
- Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
- elsif gid.model_class.respond_to?(:lazy_find)
- gid.model_class.lazy_find(gid.model_id)
- else
- gid.find
- end
+ gid
end
private
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index ca93bc15be0..42fa4a6f179 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -5,7 +5,7 @@ require 'securerandom'
module Clusters
module Applications
class Jupyter < ApplicationRecord
- VERSION = '0.9-174bbd5'
+ VERSION = '0.9.0-beta.2'
self.table_name = 'clusters_applications_jupyter'
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 593ce69b0fd..0a09000fff4 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -19,15 +19,20 @@ class ExternalWikiService < Service
def fields
[
- { type: 'text', name: 'external_wiki_url', placeholder: s_('ExternalWikiService|The URL of the external Wiki'), required: true }
+ {
+ type: 'text',
+ name: 'external_wiki_url',
+ placeholder: s_('ExternalWikiService|The URL of the external Wiki'),
+ required: true
+ }
]
end
def execute(_data)
- @response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true) rescue nil
- if @response != 200
- nil
- end
+ response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
+ response.body if response.code == 200
+ rescue
+ nil
end
def self.supported_events
diff --git a/changelogs/unreleased/15398-expiration-policies-update-api.yml b/changelogs/unreleased/15398-expiration-policies-update-api.yml
new file mode 100644
index 00000000000..250d3052a5d
--- /dev/null
+++ b/changelogs/unreleased/15398-expiration-policies-update-api.yml
@@ -0,0 +1,5 @@
+---
+title: Container expiration policies can be updated with the project api
+merge_request: 22180
+author:
+type: added
diff --git a/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml b/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml
new file mode 100644
index 00000000000..f12d460a16a
--- /dev/null
+++ b/changelogs/unreleased/22171-fix-disabling-dependency-scanning.yml
@@ -0,0 +1,5 @@
+---
+title: Check both DEPENDENCY_SCANNING_DISABLED and DS_DISABLE_DIND when executing Dependency Scanning job template
+merge_request: 22172
+author:
+type: fixed
diff --git a/changelogs/unreleased/37725-test-reports-smart-list.yml b/changelogs/unreleased/37725-test-reports-smart-list.yml
new file mode 100644
index 00000000000..48353e71e97
--- /dev/null
+++ b/changelogs/unreleased/37725-test-reports-smart-list.yml
@@ -0,0 +1,5 @@
+---
+title: Added smart virtual list component to test reports to enhance rendering performance
+merge_request: 22381
+author:
+type: performance
diff --git a/changelogs/unreleased/37963-document-var-default.yml b/changelogs/unreleased/37963-document-var-default.yml
new file mode 100644
index 00000000000..df5c1c66480
--- /dev/null
+++ b/changelogs/unreleased/37963-document-var-default.yml
@@ -0,0 +1,5 @@
+---
+title: Document MAVEN_CLI_OPTS defaults for maven project dependency scanning and update when the variable is used
+merge_request: 22126
+author:
+type: added
diff --git a/changelogs/unreleased/update-jupyterhub-chart.yml b/changelogs/unreleased/update-jupyterhub-chart.yml
new file mode 100644
index 00000000000..8e2e1ec090e
--- /dev/null
+++ b/changelogs/unreleased/update-jupyterhub-chart.yml
@@ -0,0 +1,5 @@
+---
+title: Update jupyterhub chart
+merge_request: 22127
+author:
+type: changed
diff --git a/doc/administration/gitaly/praefect.md b/doc/administration/gitaly/praefect.md
index 597be6cc97e..72c3f996841 100644
--- a/doc/administration/gitaly/praefect.md
+++ b/doc/administration/gitaly/praefect.md
@@ -25,15 +25,22 @@ The most common architecture for Praefect is simplified in the diagram below:
```mermaid
graph TB
GitLab --> Praefect;
- Praefect --> Gitaly-1;
- Praefect --> Gitaly-2;
- Praefect --> Gitaly-3;
+ Praefect --- PostgreSQL;
+ Praefect --> Gitaly1;
+ Praefect --> Gitaly2;
+ Praefect --> Gitaly3;
```
Where `GitLab` is the collection of clients that can request Git operations.
The Praefect node has three storage nodes attached. Praefect itself doesn't
store data, but connects to three Gitaly nodes, `Gitaly-1`, `Gitaly-2`, and `Gitaly-3`.
+In order to keep track of replication state, Praefect relies on a
+PostgreSQL database. This database is a single point of failure so you
+should use a highly available PostgreSQL server for this. GitLab
+itself needs a HA PostgreSQL server too, so you could optionally co-locate the Praefect
+SQL database on the PostgreSQL server you use for the rest of GitLab.
+
Praefect may be enabled on its own node or can be run on the GitLab server.
In the example below we will use a separate server, but the optimal configuration
for Praefect is still being determined.
@@ -62,6 +69,53 @@ We need to manage the following secrets and make them match across hosts:
`PRAEFECT_EXTERNAL_TOKEN` because Gitaly clients must not be able to
access internal nodes of the Praefect cluster directly; that could
lead to data loss.
+1. `PRAEFECT_SQL_PASSWORD`: this password is used by Praefect to connect to
+ PostgreSQL.
+
+#### Network addresses
+
+1. `POSTGRESQL_SERVER`: the host name or IP address of your PostgreSQL server
+
+#### PostgreSQL
+
+To set up a Praefect cluster you need a highly available PostgreSQL
+server. You need PostgreSQL 9.6 or newer. Praefect needs to have a SQL
+user with the right to create databases.
+
+In the instructions below we assume you have administrative access to
+your PostgreSQL server via `psql`. Depending on your environment, you
+may also be able to do this via the web interface of your cloud
+platform, or via your configuration management system, etc.
+
+Below we assume that you have administrative access as the `postgres`
+user. First open a `psql` session as the `postgres` user:
+
+```shell
+psql -h POSTGRESQL_SERVER -U postgres -d template1
+```
+
+Once you are connected, run the following command. Replace
+`PRAEFECT_SQL_PASSWORD` with the actual (random) password you
+generated for the `praefect` SQL user:
+
+```sql
+CREATE ROLE praefect WITH LOGIN CREATEDB PASSWORD 'PRAEFECT_SQL_PASSWORD';
+\q # exit psql
+```
+
+Now connect as the `praefect` user to create the database. This has
+the side effect of verifying that you have access:
+
+```shell
+psql -h POSTGRESQL_SERVER -U praefect -d template1
+```
+
+Once you have connected as the `praefect` user, run:
+
+```sql
+CREATE DATABASE praefect_production WITH ENCODING=UTF8;
+\q # quit psql
+```
#### Praefect
@@ -118,10 +172,39 @@ praefect['virtual_storages'] = {
}
}
}
+
+praefect['database_host'] = 'POSTGRESQL_SERVER'
+praefect['database_port'] = 5432
+praefect['database_user'] = 'praefect'
+praefect['database_password'] = 'PRAEFECT_SQL_PASSWORD'
+praefect['database_dbname'] = 'praefect_production'
+
+# Uncomment the line below if you do not want to use an encrypted
+# connection to PostgreSQL
+# praefect['database_sslmode'] = 'disable'
+
+# Uncomment and modify these lines if you are using a TLS client
+# certificate to connect to PostgreSQL
+# praefect['database_sslcert'] = '/path/to/client-cert'
+# praefect['database_sslkey'] = '/path/to/client-key'
+
+# Uncomment and modify this line if your PostgreSQL server uses a custom
+# CA
+# praefect['database_sslrootcert'] = '/path/to/rootcert'
```
Save the file and [reconfigure Praefect](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+After you reconfigure, verify that Praefect can reach PostgreSQL:
+
+```shell
+sudo -u git /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml sql-ping
+```
+
+If the check fails, make sure you have followed the steps correctly. If you edit `/etc/gitlab/gitlab.rb`,
+remember to run `sudo gitlab-ctl reconfigure` again before trying the
+`sql-ping` command.
+
#### Gitaly
Next we will configure each Gitaly server assigned to Praefect. Configuration for these
diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md
index 0c6caed4e55..e573699e856 100644
--- a/doc/administration/packages/container_registry.md
+++ b/doc/administration/packages/container_registry.md
@@ -631,7 +631,7 @@ mounting the docker-daemon and setting `privileged = false` in the Runner's
```toml
[runners.docker]
- image = "ruby:2.1"
+ image = "ruby:2.6"
privileged = false
volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
```
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 5c6dd93a676..c04071faf9c 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -529,9 +529,9 @@ type CreateSnippetPayload {
snippet: Snippet
}
-type Design implements Noteable {
+type Design implements DesignFields & Noteable {
"""
- Diff refs of the design
+ The diff refs for this design
"""
diffRefs: DiffRefs!
@@ -561,33 +561,32 @@ type Design implements Noteable {
): DiscussionConnection!
"""
- Type of change made to the design at the version specified by the `atVersion`
- argument if supplied. Defaults to the latest version
+ How this design was changed in the current version
"""
event: DesignVersionEvent!
"""
- Filename of the design file
+ The filename of the design
"""
filename: String!
"""
- Full path of the design file
+ The full path to the design file
"""
fullPath: String!
"""
- ID of the design
+ The ID of this design
"""
id: ID!
"""
- Image of the design
+ The URL of the image
"""
image: String!
"""
- Issue associated with the design
+ The issue the design belongs to
"""
issue: Issue!
@@ -617,17 +616,17 @@ type Design implements Noteable {
): NoteConnection!
"""
- Total count of user-created notes for the design
+ The total count of user-created notes for this design
"""
notesCount: Int!
"""
- Project associated with the design
+ The project the design belongs to
"""
project: Project!
"""
- All versions related to the design, ordered newest first
+ All versions related to this design ordered newest first
"""
versions(
"""
@@ -765,6 +764,53 @@ type DesignEdge {
node: Design
}
+interface DesignFields {
+ """
+ The diff refs for this design
+ """
+ diffRefs: DiffRefs!
+
+ """
+ How this design was changed in the current version
+ """
+ event: DesignVersionEvent!
+
+ """
+ The filename of the design
+ """
+ filename: String!
+
+ """
+ The full path to the design file
+ """
+ fullPath: String!
+
+ """
+ The ID of this design
+ """
+ id: ID!
+
+ """
+ The URL of the image
+ """
+ image: String!
+
+ """
+ The issue the design belongs to
+ """
+ issue: Issue!
+
+ """
+ The total count of user-created notes for this design
+ """
+ notesCount: Int!
+
+ """
+ The project the design belongs to
+ """
+ project: Project!
+}
+
"""
Autogenerated input type of DesignManagementDelete
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 1357cab54fe..8730cccf1ed 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -10350,7 +10350,7 @@
"fields": [
{
"name": "diffRefs",
- "description": "Diff refs of the design",
+ "description": "The diff refs for this design",
"args": [
],
@@ -10425,7 +10425,7 @@
},
{
"name": "event",
- "description": "Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version",
+ "description": "How this design was changed in the current version",
"args": [
],
@@ -10443,7 +10443,7 @@
},
{
"name": "filename",
- "description": "Filename of the design file",
+ "description": "The filename of the design",
"args": [
],
@@ -10461,7 +10461,7 @@
},
{
"name": "fullPath",
- "description": "Full path of the design file",
+ "description": "The full path to the design file",
"args": [
],
@@ -10479,7 +10479,7 @@
},
{
"name": "id",
- "description": "ID of the design",
+ "description": "The ID of this design",
"args": [
],
@@ -10497,7 +10497,7 @@
},
{
"name": "image",
- "description": "Image of the design",
+ "description": "The URL of the image",
"args": [
],
@@ -10515,7 +10515,7 @@
},
{
"name": "issue",
- "description": "Issue associated with the design",
+ "description": "The issue the design belongs to",
"args": [
],
@@ -10590,7 +10590,7 @@
},
{
"name": "notesCount",
- "description": "Total count of user-created notes for the design",
+ "description": "The total count of user-created notes for this design",
"args": [
],
@@ -10608,7 +10608,7 @@
},
{
"name": "project",
- "description": "Project associated with the design",
+ "description": "The project the design belongs to",
"args": [
],
@@ -10626,7 +10626,7 @@
},
{
"name": "versions",
- "description": "All versions related to the design, ordered newest first",
+ "description": "All versions related to this design ordered newest first",
"args": [
{
"name": "after",
@@ -10688,12 +10688,196 @@
"kind": "INTERFACE",
"name": "Noteable",
"ofType": null
+ },
+ {
+ "kind": "INTERFACE",
+ "name": "DesignFields",
+ "ofType": null
}
],
"enumValues": null,
"possibleTypes": null
},
{
+ "kind": "INTERFACE",
+ "name": "DesignFields",
+ "description": null,
+ "fields": [
+ {
+ "name": "diffRefs",
+ "description": "The diff refs for this design",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "DiffRefs",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "event",
+ "description": "How this design was changed in the current version",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "ENUM",
+ "name": "DesignVersionEvent",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "filename",
+ "description": "The filename of the design",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "fullPath",
+ "description": "The full path to the design file",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "id",
+ "description": "The ID of this design",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "image",
+ "description": "The URL of the image",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "issue",
+ "description": "The issue the design belongs to",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Issue",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "notesCount",
+ "description": "The total count of user-created notes for this design",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "project",
+ "description": "The project the design belongs to",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Project",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": [
+ {
+ "kind": "OBJECT",
+ "name": "Design",
+ "ofType": null
+ }
+ ]
+ },
+ {
"kind": "ENUM",
"name": "DesignVersionEvent",
"description": "Mutation event of a Design within a Version",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 8ca17d4107d..086c80415ad 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -104,15 +104,15 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description |
| --- | ---- | ---------- |
-| `id` | ID! | ID of the design |
-| `project` | Project! | Project associated with the design |
-| `issue` | Issue! | Issue associated with the design |
-| `notesCount` | Int! | Total count of user-created notes for the design |
-| `filename` | String! | Filename of the design file |
-| `fullPath` | String! | Full path of the design file |
-| `event` | DesignVersionEvent! | Type of change made to the design at the version specified by the `atVersion` argument if supplied. Defaults to the latest version |
-| `image` | String! | Image of the design |
-| `diffRefs` | DiffRefs! | Diff refs of the design |
+| `id` | ID! | The ID of this design |
+| `project` | Project! | The project the design belongs to |
+| `issue` | Issue! | The issue the design belongs to |
+| `filename` | String! | The filename of the design |
+| `fullPath` | String! | The full path to the design file |
+| `image` | String! | The URL of the image |
+| `diffRefs` | DiffRefs! | The diff refs for this design |
+| `event` | DesignVersionEvent! | How this design was changed in the current version |
+| `notesCount` | Int! | The total count of user-created notes for this design |
### DesignCollection
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 33308ff8905..a856c01eb00 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -761,6 +761,14 @@ GET /projects/:id
"snippets_enabled": false,
"resolve_outdated_diff_discussions": false,
"container_registry_enabled": false,
+ "container_expiration_policy": {
+ "cadence": "7d",
+ "enabled": false,
+ "keep_n": null,
+ "older_than": null,
+ "name_regex": null,
+ "next_run_at": "2020-01-07T21:42:58.658Z"
+ },
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
@@ -986,6 +994,7 @@ POST /projects
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
+| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
@@ -1115,6 +1124,7 @@ PUT /projects/:id
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
+| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 2d1abed0dc5..8c6069bd939 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -32,14 +32,14 @@ A one-line example can be seen below:
sudo gitlab-runner register \
--url "https://gitlab.example.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "docker-ruby-2.1" \
+ --description "docker-ruby:2.6" \
--executor "docker" \
- --docker-image ruby:2.1 \
+ --docker-image ruby:2.6 \
--docker-services postgres:latest \
--docker-services mysql:latest
```
-The registered runner will use the `ruby:2.1` Docker image and will run two
+The registered runner will use the `ruby:2.6` Docker image and will run two
services, `postgres:latest` and `mysql:latest`, both of which will be
accessible during the build process.
@@ -194,7 +194,7 @@ services that you want to use during build time:
```yaml
default:
- image: ruby:2.2
+ image: ruby:2.6
services:
- postgres:9.3
@@ -214,15 +214,15 @@ default:
before_script:
- bundle install
-test:2.1:
- image: ruby:2.1
+test:2.6:
+ image: ruby:2.6
services:
- postgres:9.3
script:
- bundle exec rake spec
-test:2.2:
- image: ruby:2.2
+test:2.7:
+ image: ruby:2.7
services:
- postgres:9.4
script:
@@ -235,7 +235,7 @@ for `image` and `services`:
```yaml
default:
image:
- name: ruby:2.2
+ name: ruby:2.6
entrypoint: ["/bin/bash"]
services:
@@ -277,7 +277,7 @@ services:
command: ["postgres"]
image:
- name: ruby:2.2
+ name: ruby:2.6
entrypoint: ["/bin/bash"]
before_script:
@@ -773,7 +773,7 @@ time.
1. Create any service container: `mysql`, `postgresql`, `mongodb`, `redis`.
1. Create cache container to store all volumes as defined in `config.toml` and
- `Dockerfile` of build image (`ruby:2.1` as in above example).
+ `Dockerfile` of build image (`ruby:2.6` as in above example).
1. Create build container and link any service container to build container.
1. Start build container and send job script to the container.
1. Run job script.
@@ -818,11 +818,11 @@ Finally, create a build container by executing the `build_script` file we
created earlier:
```sh
-docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
+docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.6 /bin/bash < build_script
```
The above command will create a container named `build` that is spawned from
-the `ruby:2.1` image and has two services linked to it. The `build_script` is
+the `ruby:2.6` image and has two services linked to it. The `build_script` is
piped using STDIN to the bash interpreter which in turn executes the
`build_script` in the `build` container.
diff --git a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
index 9a4fbfcce6d..66246a0fda2 100644
--- a/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
+++ b/doc/ci/examples/test-and-deploy-ruby-application-to-heroku.md
@@ -71,12 +71,12 @@ gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "PROJECT_REGISTRATION_TOKEN" \
- --description "ruby-2.2" \
+ --description "ruby:2.6" \
--executor "docker" \
- --docker-image ruby:2.2 \
+ --docker-image ruby:2.6 \
--docker-postgres latest
```
-With the command above, you create a Runner that uses the [ruby:2.2](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database.
+With the command above, you create a Runner that uses the [ruby:2.6](https://hub.docker.com/_/ruby) image and uses a [postgres](https://hub.docker.com/_/postgres) database.
To access the PostgreSQL database, connect to `host: postgres` as user `postgres` with no password.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 3fc9f75808f..1d735f4e221 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -3645,7 +3645,7 @@ having their own custom `script` defined:
```yaml
.job_template: &job_definition # Hidden key that defines an anchor named 'job_definition'
- image: ruby:2.1
+ image: ruby:2.6
services:
- postgres
- redis
@@ -3667,13 +3667,13 @@ given hash into the current one", and `*` includes the named anchor
```yaml
.job_template:
- image: ruby:2.1
+ image: ruby:2.6
services:
- postgres
- redis
test1:
- image: ruby:2.1
+ image: ruby:2.6
services:
- postgres
- redis
@@ -3681,7 +3681,7 @@ test1:
- test1 project
test2:
- image: ruby:2.1
+ image: ruby:2.6
services:
- postgres
- redis
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 786b590ec70..94285efdf1e 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -382,7 +382,7 @@ end
## String Freezing
In recent Ruby versions calling `freeze` on a String leads to it being allocated
-only once and re-used. For example, on Ruby 2.3 this will only allocate the
+only once and re-used. For example, on Ruby 2.3 or later this will only allocate the
"foo" String once:
```ruby
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 01feaaac423..59f241db7de 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -146,7 +146,7 @@ using environment variables.
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
| `PIP_REQUIREMENTS_FILE` | Pip requirements file to be scanned. |
-| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). |
+| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to `maven` by the analyzer. The default is `"-DskipTests --batch-mode"`. See an example for [using private repos](#using-private-maven-repos). |
| `BUNDLER_AUDIT_UPDATE_DISABLED` | Disable automatic updates for the `bundler-audit` analyzer (default: `"false"`). Useful if you're running Dependency Scanning in an offline, air-gapped environment.|
### Using private Maven repos
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 27bd9da8d18..263b20ea224 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -1,5 +1,5 @@
---
-last_updated: 2019-06-04
+last_updated: 2020-01-06
type: reference, howto
---
@@ -158,7 +158,7 @@ first thing GitLab Runner will look for in your `.gitlab-ci.yml` is a
your container to run that script:
```yaml
-image: ruby:2.3
+image: ruby:2.7
pages:
script:
@@ -170,9 +170,9 @@ pages:
```
In this case, you're telling the Runner to pull this image, which
-contains Ruby 2.3 as part of its file system. When you don't specify
+contains Ruby 2.7 as part of its file system. When you don't specify
this image in your configuration, the Runner will use a default
-image, which is Ruby 2.1.
+image, which is Ruby 2.6.
If your SSG needs [NodeJS](https://nodejs.org/) to build, you'll
need to specify which image you want to use, and this image should
@@ -198,7 +198,7 @@ To do that, we need to add another line to our CI, telling the Runner
to only perform that _job_ called `pages` on the `master` branch `only`:
```yaml
-image: ruby:2.3
+image: ruby:2.6
pages:
script:
@@ -221,7 +221,7 @@ and deploy. To specify which stage your _job_ is running,
simply add another line to your CI:
```yaml
-image: ruby:2.3
+image: ruby:2.6
pages:
stage: deploy
@@ -244,7 +244,7 @@ let's add another task (_job_) to our CI, telling it to
test every push to other branches, `except` the `master` branch:
```yaml
-image: ruby:2.3
+image: ruby:2.6
pages:
stage: deploy
@@ -294,7 +294,7 @@ every single _job_. In our example, notice that we run
We don't need to repeat it:
```yaml
-image: ruby:2.3
+image: ruby:2.6
before_script:
- bundle install
@@ -329,7 +329,7 @@ cache Jekyll dependencies in a `vendor` directory
when we run `bundle install`:
```yaml
-image: ruby:2.3
+image: ruby:2.6
cache:
paths:
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 01e1909f6d6..37b5e77c062 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,6 +1,6 @@
---
type: reference
-last_updated: 2018-06-04
+last_updated: 2020-01-06
---
# Exploring GitLab Pages
@@ -156,7 +156,7 @@ Below is a copy of `.gitlab-ci.yml` where the most significant line is the last
one, specifying to execute everything in the `pages` branch:
```
-image: ruby:2.1
+image: ruby:2.6
pages:
script:
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index c28d6797c56..e6803d48a47 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -178,6 +178,15 @@ module API
expose :only_protected_branches
end
+ class ContainerExpirationPolicy < Grape::Entity
+ expose :cadence
+ expose :enabled
+ expose :keep_n
+ expose :older_than
+ expose :name_regex
+ expose :next_run_at
+ end
+
class ProjectImportStatus < ProjectIdentity
expose :import_status
@@ -276,6 +285,8 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :resolve_outdated_diff_discussions
expose :container_registry_enabled
+ expose :container_expiration_policy, using: Entities::ContainerExpirationPolicy,
+ if: -> (project, _) { project.container_expiration_policy }
# Expose old field names with the new permissions methods to keep API compatible
# TODO: remove in API v5, replaced by *_access_level
@@ -341,6 +352,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
super(projects_relation).preload(:group)
.preload(:ci_cd_settings)
+ .preload(:container_expiration_policy)
.preload(:auto_devops)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 0ca5b73e270..e4f943bd925 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -32,6 +32,9 @@ module API
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :remove_source_branch_after_merge, type: Boolean, desc: 'Remove the source branch by default after merge'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
+ optional :container_expiration_policy_attributes, type: Hash do
+ use :optional_container_expiration_policy_params
+ end
optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project'
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the project.'
optional :public_builds, type: Boolean, desc: 'Perform public builds'
@@ -72,6 +75,14 @@ module API
params :optional_update_params_ee do
end
+ params :optional_container_expiration_policy_params do
+ optional :cadence, type: String, desc: 'Container expiration policy cadence for recurring job'
+ optional :keep_n, type: String, desc: 'Container expiration policy number of images to keep'
+ optional :older_than, type: String, desc: 'Container expiration policy remove images older than value'
+ optional :name_regex, type: String, desc: 'Container expiration policy regex for image removal'
+ optional :enabled, type: Boolean, desc: 'Flag indication if container expiration policy is enabled'
+ end
+
def self.update_params_at_least_one_of
[
:auto_devops_enabled,
@@ -84,6 +95,7 @@ module API
:ci_config_path,
:ci_default_git_depth,
:container_registry_enabled,
+ :container_expiration_policy_attributes,
:default_branch,
:description,
:autoclose_referenced_issues,
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index e979a6f2de1..6f270897d93 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -77,6 +77,7 @@ dependency_scanning:
services: []
except:
variables:
+ - $DEPENDENCY_SCANNING_DISABLED
- $DS_DISABLE_DIND == 'false'
script:
- /analyzer run
diff --git a/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb b/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb
new file mode 100644
index 00000000000..bce0819b700
--- /dev/null
+++ b/lib/gitlab/database_importers/self_monitoring/project/delete_service.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module SelfMonitoring
+ module Project
+ class DeleteService < ::BaseService
+ include Stepable
+ include SelfMonitoring::Helpers
+
+ steps :validate_self_monitoring_project_exists,
+ :destroy_project_owner,
+ :delete_project_id
+
+ def initialize
+ super(nil)
+ end
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def validate_self_monitoring_project_exists(result)
+ unless project_created? || self_monitoring_project_id.present?
+ return error(_('Self monitoring project does not exist'))
+ end
+
+ success(result)
+ end
+
+ def destroy_project_owner(result)
+ return success(result) unless project_created?
+
+ if self_monitoring_project.owner.destroy
+ success(result)
+ else
+ log_error(self_monitoring_project.errors.full_messages)
+ error(_('Error deleting project. Check logs for error details.'))
+ end
+ end
+
+ def delete_project_id(result)
+ update_result = application_settings.update(
+ instance_administration_project_id: nil
+ )
+
+ if update_result
+ success(result)
+ else
+ log_error("Could not delete self monitoring project ID, errors: %{errors}" % { errors: application_settings.errors.full_messages })
+ error(_('Could not delete project ID'))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c584defb2e4..56771654526 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5121,6 +5121,9 @@ msgstr ""
msgid "Could not delete chat nickname %{chat_name}."
msgstr ""
+msgid "Could not delete project ID"
+msgstr ""
+
msgid "Could not fetch projects"
msgstr ""
@@ -7122,6 +7125,9 @@ msgstr ""
msgid "Error deleting %{issuableType}"
msgstr ""
+msgid "Error deleting project. Check logs for error details."
+msgstr ""
+
msgid "Error details"
msgstr ""
@@ -16262,6 +16268,9 @@ msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
+msgid "Self monitoring project does not exist"
+msgstr ""
+
msgid "Self-monitoring is not enabled on this GitLab server, contact your administrator."
msgstr ""
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index dcf3c989047..2ec477fc494 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -124,14 +124,26 @@ describe GitlabSchema do
describe '.object_from_id' do
context 'for subclasses of `ApplicationRecord`' do
- it 'returns the correct record' do
- user = create(:user)
+ let_it_be(:user) { create(:user) }
+ it 'returns the correct record' do
result = described_class.object_from_id(user.to_global_id.to_s)
expect(result.sync).to eq(user)
end
+ it 'returns the correct record, of the expected type' do
+ result = described_class.object_from_id(user.to_global_id.to_s, expected_type: ::User)
+
+ expect(result.sync).to eq(user)
+ end
+
+ it 'fails if the type does not match' do
+ expect do
+ described_class.object_from_id(user.to_global_id.to_s, expected_type: ::Project)
+ end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+
it 'batchloads the queries' do
user1 = create(:user)
user2 = create(:user)
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index b2d0ba27d4e..39a363cb913 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -7,7 +7,16 @@ describe GitlabSchema.types['Query'] do
expect(described_class.graphql_name).to eq('Query')
end
- it { is_expected.to have_graphql_fields(:project, :namespace, :group, :echo, :metadata, :current_user, :snippets) }
+ it do
+ is_expected.to have_graphql_fields(:project,
+ :namespace,
+ :group,
+ :echo,
+ :metadata,
+ :current_user,
+ :snippets
+ ).at_least
+ end
describe 'namespace field' do
subject { described_class.fields['namespace'] }
diff --git a/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
new file mode 100644
index 00000000000..b0cec61ce06
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/delete_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::DatabaseImporters::SelfMonitoring::Project::DeleteService do
+ describe '#execute' do
+ let(:result) { subject.execute }
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+ before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+ end
+
+ context 'when project does not exist' do
+ it 'returns error' do
+ expect(result).to eq(
+ status: :error,
+ message: 'Self monitoring project does not exist',
+ last_step: :validate_self_monitoring_project_exists
+ )
+ end
+ end
+
+ context 'with project destroyed but ID still present in application settings' do
+ before do
+ application_setting.instance_administration_project_id = 1
+ end
+
+ it 'deletes project ID from application settings' do
+ subject.execute
+
+ expect(application_setting.instance_administration_project_id).to be_nil
+ end
+ end
+
+ context 'when self monitoring project exists' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+
+ before do
+ application_setting.instance_administration_project = project
+ end
+
+ it 'destroys project' do
+ subject.execute
+
+ expect { project.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'deletes project ID from application settings' do
+ subject.execute
+
+ expect(application_setting.instance_administration_project_id).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 0ec9333d6a7..3bc5088d1ab 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -57,7 +57,8 @@ describe Clusters::Applications::Jupyter do
it 'is initialized with 4 arguments' do
expect(subject.name).to eq('jupyter')
expect(subject.chart).to eq('jupyter/jupyterhub')
- expect(subject.version).to eq('0.9-174bbd5')
+ expect(subject.version).to eq('0.9.0-beta.2')
+
expect(subject).to be_rbac
expect(subject.repository).to eq('https://jupyterhub.github.io/helm-chart/')
expect(subject.files).to eq(jupyter.files)
@@ -75,7 +76,7 @@ describe Clusters::Applications::Jupyter do
let(:jupyter) { create(:clusters_applications_jupyter, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
- expect(subject.version).to eq('0.9-174bbd5')
+ expect(subject.version).to eq('0.9.0-beta.2')
end
end
end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 808659552ff..441f8265629 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#code' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
context 'with deployment' do
generate_cycle_analytics_spec(
@@ -24,8 +25,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]],
post_fn: -> (context, data) do
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
- context.deploy_master(context.user, context.project)
end)
context "when a regular merge request (that doesn't close the issue) is created" do
@@ -56,7 +55,6 @@ describe 'CycleAnalytics#code' do
context.create_merge_request_closing_issue(context.user, context.project, data[:issue])
end]],
post_fn: -> (context, data) do
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when a regular merge request (that doesn't close the issue) is created" do
diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb
index 0d2c14c29dd..03fe8c3b50b 100644
--- a/spec/models/cycle_analytics/group_level_spec.rb
+++ b/spec/models/cycle_analytics/group_level_spec.rb
@@ -3,12 +3,12 @@
require 'spec_helper'
describe CycleAnalytics::GroupLevel do
- let(:group) { create(:group)}
- let(:project) { create(:project, :repository, namespace: group) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:group) { create(:group)}
+ let_it_be(:project) { create(:project, :repository, namespace: group) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
- let(:milestone) { create(:milestone, project: project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 8cdf83b1292..726f2f8b018 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#issue' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
generate_cycle_analytics_spec(
phase: :issue,
@@ -28,10 +29,6 @@ describe 'CycleAnalytics#issue' do
end
end]],
post_fn: -> (context, data) do
- if data[:issue].persisted?
- context.create_merge_request_closing_issue(context.user, context.project, data[:issue].reload)
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
- end
end)
context "when a regular label (instead of a list label) is added to the issue" do
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 28ad9bd194d..3bd9f317ca7 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -5,17 +5,18 @@ require 'spec_helper'
describe 'CycleAnalytics#plan' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
generate_cycle_analytics_spec(
phase: :plan,
data_fn: -> (context) do
{
- issue: context.create(:issue, project: context.project),
+ issue: context.build(:issue, project: context.project),
branch_name: context.generate(:branch)
}
end,
@@ -32,8 +33,6 @@ describe 'CycleAnalytics#plan' do
context.create_commit_referencing_issue(data[:issue], branch_name: data[:branch_name])
end]],
post_fn: -> (context, data) do
- context.create_merge_request_closing_issue(context.user, context.project, data[:issue], source_branch: data[:branch_name])
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when a regular label (instead of a list label) is added to the issue" do
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 613c1786540..01d88bbeec9 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#production' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
generate_cycle_analytics_spec(
phase: :production,
@@ -24,13 +25,7 @@ describe 'CycleAnalytics#production' do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
- sha = context.project.repository.create_file(
- context.user,
- context.generate(:branch),
- 'content',
- message: 'commit message',
- branch_name: 'master')
- context.project.repository.commit(sha)
+ context.project.repository.commit("sha_that_does_not_matter")
context.deploy_master(context.user, context.project)
end]])
@@ -47,7 +42,7 @@ describe 'CycleAnalytics#production' do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
- issue = create(:issue, project: project)
+ issue = build(:issue, project: project)
merge_request = create_merge_request_closing_issue(user, project, issue)
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 351eb139416..2fc81777746 100644
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
describe CycleAnalytics::ProjectLevel do
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
- let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
- let(:milestone) { create(:milestone, project: project) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index ef88fd86340..50670188e85 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'CycleAnalytics#review' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 571792559d8..cf0695f175a 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -5,11 +5,12 @@ require 'spec_helper'
describe 'CycleAnalytics#staging' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
generate_cycle_analytics_spec(
phase: :staging,
@@ -28,14 +29,7 @@ describe 'CycleAnalytics#staging' do
["production deploy happens after merge request is merged (along with other changes)",
lambda do |context, data|
# Make other changes on master
- sha = context.project.repository.create_file(
- context.user,
- context.generate(:branch),
- 'content',
- message: 'commit message',
- branch_name: 'master')
- context.project.repository.commit(sha)
-
+ context.project.repository.commit("this_sha_apparently_does_not_matter")
context.deploy_master(context.user, context.project)
end]])
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index 7b3001d2bd8..24800aafca7 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -5,16 +5,19 @@ require 'spec_helper'
describe 'CycleAnalytics#test' do
extend CycleAnalyticsHelpers::TestGeneration
- let(:project) { create(:project, :repository) }
- let(:from_date) { 10.days.ago }
- let(:user) { create(:user, :admin) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:from_date) { 10.days.ago }
+ let_it_be(:user) { create(:user, :admin) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:project_level) { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ let!(:merge_request) { create_merge_request_closing_issue(user, project, issue) }
- subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
+ subject { project_level }
generate_cycle_analytics_spec(
phase: :test,
data_fn: lambda do |context|
- issue = context.create(:issue, project: context.project)
+ issue = context.issue
merge_request = context.create_merge_request_closing_issue(context.user, context.project, issue)
pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project, head_pipeline_of: merge_request)
{ pipeline: pipeline, issue: issue }
@@ -22,20 +25,15 @@ describe 'CycleAnalytics#test' do
start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]],
end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]],
post_fn: -> (context, data) do
- context.merge_merge_requests_closing_issue(context.user, context.project, data[:issue])
end)
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.succeed!
- merge_merge_requests_closing_issue(user, project, issue)
-
expect(subject[:test].project_median).to be_nil
end
end
@@ -53,30 +51,22 @@ describe 'CycleAnalytics#test' do
context "when the pipeline is dropped (failed)" do
it "returns nil" do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.drop!
- merge_merge_requests_closing_issue(user, project, issue)
-
expect(subject[:test].project_median).to be_nil
end
end
context "when the pipeline is cancelled" do
it "returns nil" do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(user, project, issue)
pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
pipeline.run!
pipeline.cancel!
- merge_merge_requests_closing_issue(user, project, issue)
-
expect(subject[:test].project_median).to be_nil
end
end
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index bdd8605436f..f8d88a944a5 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -26,4 +26,34 @@ describe ExternalWikiService do
it { is_expected.not_to validate_presence_of(:external_wiki_url) }
end
end
+
+ describe 'test' do
+ before do
+ subject.properties['external_wiki_url'] = url
+ end
+
+ let(:url) { 'http://foo' }
+ let(:data) { nil }
+ let(:result) { subject.test(data) }
+
+ context 'the URL is not reachable' do
+ before do
+ WebMock.stub_request(:get, url).to_return(status: 404, body: 'not a page')
+ end
+
+ it 'is not successful' do
+ expect(result[:success]).to be_falsey
+ end
+ end
+
+ context 'the URL is reachable' do
+ before do
+ WebMock.stub_request(:get, url).to_return(status: 200, body: 'foo')
+ end
+
+ it 'is successful' do
+ expect(result[:success]).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 03efbc6bce7..f2d2cdba480 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -2251,6 +2251,22 @@ describe API::Projects do
put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(403)
end
+
+ it 'updates container_expiration_policy' do
+ project_param = {
+ container_expiration_policy_attributes: {
+ cadence: '1month',
+ keep_n: 1
+ }
+ }
+
+ put api("/projects/#{project3.id}", user4), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['container_expiration_policy']['cadence']).to eq('1month')
+ expect(json_response['container_expiration_policy']['keep_n']).to eq(1)
+ end
end
context 'when authenticated as project developer' do
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 2096ec90c5b..2fac6bfb30b 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -27,6 +27,8 @@ module CycleAnalyticsHelpers
scenarios = combinations_of_start_time_conditions.product(combinations_of_end_time_conditions)
scenarios.each do |start_time_conditions, end_time_conditions|
+ let_it_be(:other_project) { create(:project, :repository) }
+
context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
it "finds the median of available durations between the two conditions", :sidekiq_might_not_need_inline do
@@ -56,8 +58,6 @@ module CycleAnalyticsHelpers
end
context "when the data belongs to another project" do
- let(:other_project) { create(:project, :repository) }
-
it "returns nil" do
# Use a stub to "trick" the data/condition functions
# into using another project. This saves us from having to
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index e21b3aea3da..6d9c27d0255 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -105,12 +105,15 @@ module GraphqlHelpers
end
def query_graphql_field(name, attributes = {}, fields = nil)
- fields ||= all_graphql_fields_for(name.classify)
- attributes = attributes_to_graphql(attributes)
- attributes = "(#{attributes})" if attributes.present?
+ field_params = if attributes.present?
+ "(#{attributes_to_graphql(attributes)})"
+ else
+ ''
+ end
+
<<~QUERY
- #{name}#{attributes}
- #{wrap_fields(fields)}
+ #{GraphqlHelpers.fieldnamerize(name.to_s)}#{field_params}
+ #{wrap_fields(fields || all_graphql_fields_for(name.to_s.classify))}
QUERY
end
@@ -301,6 +304,17 @@ module GraphqlHelpers
def global_id_of(model)
model.to_global_id.to_s
end
+
+ def missing_required_argument(path, argument)
+ a_hash_including(
+ 'path' => ['query'].concat(path),
+ 'extensions' => a_hash_including('code' => 'missingRequiredArguments', 'arguments' => argument.to_s)
+ )
+ end
+
+ def custom_graphql_error(path, msg)
+ a_hash_including('path' => path, 'message' => msg)
+ end
end
# This warms our schema, doing this as part of loading the helpers to avoid
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 282deea4606..e151a934591 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -11,8 +11,22 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
Array.wrap(expected).map { |name| GraphqlHelpers.fieldnamerize(name) }
end
+ @allow_extra = false
+
+ chain :only do
+ @allow_extra = false
+ end
+
+ chain :at_least do
+ @allow_extra = true
+ end
+
match do |kls|
- expect(kls.fields.keys).to contain_exactly(*expected_field_names)
+ if @allow_extra
+ expect(kls.fields.keys).to include(*expected_field_names)
+ else
+ expect(kls.fields.keys).to contain_exactly(*expected_field_names)
+ end
end
failure_message do |kls|
@@ -22,7 +36,7 @@ RSpec::Matchers.define :have_graphql_fields do |*expected|
message = []
message << "is missing fields: <#{missing.inspect}>" if missing.any?
- message << "contained unexpected fields: <#{extra.inspect}>" if extra.any?
+ message << "contained unexpected fields: <#{extra.inspect}>" if extra.any? && !@allow_extra
message.join("\n")
end
diff --git a/spec/support/shared_examples/graphql/failure_to_find_anything.rb b/spec/support/shared_examples/graphql/failure_to_find_anything.rb
new file mode 100644
index 00000000000..b2533c992c1
--- /dev/null
+++ b/spec/support/shared_examples/graphql/failure_to_find_anything.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# Shared example for legal queries that are expected to return nil.
+# Requires the following let bindings to be defined:
+# - post_query: action to send the query
+# - path: array of keys from query root to the result
+shared_examples 'a failure to find anything' do
+ it 'finds nothing' do
+ post_query
+
+ data = graphql_data.dig(*path)
+
+ expect(data).to be_nil
+ end
+end