Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fde9b7a784
commit
1b9b475faa
|
@ -124,6 +124,9 @@ export default {
|
||||||
isLoading() {
|
isLoading() {
|
||||||
return this.$apollo.queries.issuable.loading || this.loading;
|
return this.$apollo.queries.issuable.loading || this.loading;
|
||||||
},
|
},
|
||||||
|
initialLoading() {
|
||||||
|
return this.$apollo.queries.issuable.loading;
|
||||||
|
},
|
||||||
hasDate() {
|
hasDate() {
|
||||||
return this.dateValue !== null;
|
return this.dateValue !== null;
|
||||||
},
|
},
|
||||||
|
@ -271,10 +274,10 @@ export default {
|
||||||
<span class="collapse-truncated-title">{{ formattedDate }}</span>
|
<span class="collapse-truncated-title">{{ formattedDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<sidebar-inherit-date
|
<sidebar-inherit-date
|
||||||
v-if="canInherit"
|
v-if="canInherit && !initialLoading"
|
||||||
:issuable="issuable"
|
:issuable="issuable"
|
||||||
:is-loading="isLoading"
|
|
||||||
:date-type="dateType"
|
:date-type="dateType"
|
||||||
|
:is-loading="isLoading"
|
||||||
@reset-date="setDate(null)"
|
@reset-date="setDate(null)"
|
||||||
@set-date="setFixedDate"
|
@set-date="setFixedDate"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -17,8 +17,9 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
isLoading: {
|
isLoading: {
|
||||||
required: true,
|
required: false,
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
},
|
},
|
||||||
dateType: {
|
dateType: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -31,6 +32,7 @@ export default {
|
||||||
return this.issuable?.[dateFields[this.dateType].isDateFixed] || false;
|
return this.issuable?.[dateFields[this.dateType].isDateFixed] || false;
|
||||||
},
|
},
|
||||||
set(fixed) {
|
set(fixed) {
|
||||||
|
if (fixed === this.issuable[dateFields[this.dateType].isDateFixed]) return;
|
||||||
this.$emit('set-date', fixed);
|
this.$emit('set-date', fixed);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Resolvers
|
||||||
|
module Ci
|
||||||
|
# NOTE: This class was introduced to allow modifying the meaning of certain values in RunnerStatusEnum
|
||||||
|
# while preserving backward compatibility. It can be removed in 15.0 once the API has stabilized.
|
||||||
|
class RunnerStatusResolver < BaseResolver
|
||||||
|
type Types::Ci::RunnerStatusEnum, null: false
|
||||||
|
|
||||||
|
alias_method :runner, :object
|
||||||
|
|
||||||
|
argument :legacy_mode,
|
||||||
|
type: GraphQL::Types::String,
|
||||||
|
default_value: '14.5',
|
||||||
|
required: false,
|
||||||
|
description: 'Compatibility mode. A null value turns off compatibility mode.',
|
||||||
|
deprecated: { reason: 'Will be removed in 15.0. From that release onward, the field will behave as if legacyMode is null', milestone: '14.6' }
|
||||||
|
|
||||||
|
def resolve(legacy_mode:, **args)
|
||||||
|
runner.status(legacy_mode)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,24 +5,33 @@ module Types
|
||||||
class RunnerStatusEnum < BaseEnum
|
class RunnerStatusEnum < BaseEnum
|
||||||
graphql_name 'CiRunnerStatus'
|
graphql_name 'CiRunnerStatus'
|
||||||
|
|
||||||
::Ci::Runner::AVAILABLE_STATUSES.each do |status|
|
value 'ACTIVE',
|
||||||
description = case status
|
description: 'Runner that is not paused.',
|
||||||
when 'active'
|
deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
|
||||||
"A runner that is not paused."
|
value: :active
|
||||||
when 'online'
|
|
||||||
"A runner that contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}."
|
|
||||||
when 'offline'
|
|
||||||
"A runner that has not contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}."
|
|
||||||
when 'not_connected'
|
|
||||||
"A runner that has never contacted this instance."
|
|
||||||
else
|
|
||||||
"A runner that is #{status.to_s.tr('_', ' ')}."
|
|
||||||
end
|
|
||||||
|
|
||||||
value status.to_s.upcase,
|
value 'PAUSED',
|
||||||
description: description,
|
description: 'Runner that is paused.',
|
||||||
value: status.to_sym
|
deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
|
||||||
end
|
value: :paused
|
||||||
|
|
||||||
|
value 'ONLINE',
|
||||||
|
description: "Runner that contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}.",
|
||||||
|
value: :online
|
||||||
|
|
||||||
|
value 'OFFLINE',
|
||||||
|
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}.",
|
||||||
|
deprecated: { reason: 'This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period offline', milestone: '14.6' },
|
||||||
|
value: :offline
|
||||||
|
|
||||||
|
value 'STALE',
|
||||||
|
description: "Runner that has not contacted this instance within the last #{::Ci::Runner::STALE_TIMEOUT.inspect}. Only available if legacyMode is null. Will be a possible return value starting in 15.0",
|
||||||
|
value: :stale
|
||||||
|
|
||||||
|
value 'NOT_CONNECTED',
|
||||||
|
description: 'Runner that has never contacted this instance.',
|
||||||
|
deprecated: { reason: 'This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact', milestone: '14.6' },
|
||||||
|
value: :not_connected
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,8 +27,11 @@ module Types
|
||||||
description: 'Access level of the runner.'
|
description: 'Access level of the runner.'
|
||||||
field :active, GraphQL::Types::Boolean, null: false,
|
field :active, GraphQL::Types::Boolean, null: false,
|
||||||
description: 'Indicates the runner is allowed to receive jobs.'
|
description: 'Indicates the runner is allowed to receive jobs.'
|
||||||
field :status, ::Types::Ci::RunnerStatusEnum, null: false,
|
field :status,
|
||||||
description: 'Status of the runner.'
|
Types::Ci::RunnerStatusEnum,
|
||||||
|
null: false,
|
||||||
|
description: 'Status of the runner.',
|
||||||
|
resolver: ::Resolvers::Ci::RunnerStatusResolver
|
||||||
field :version, GraphQL::Types::String, null: true,
|
field :version, GraphQL::Types::String, null: true,
|
||||||
description: 'Version of the runner.'
|
description: 'Version of the runner.'
|
||||||
field :short_sha, GraphQL::Types::String, null: true,
|
field :short_sha, GraphQL::Types::String, null: true,
|
||||||
|
@ -50,7 +53,7 @@ module Types
|
||||||
field :job_count, GraphQL::Types::Int, null: true,
|
field :job_count, GraphQL::Types::Int, null: true,
|
||||||
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
|
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
|
||||||
field :admin_url, GraphQL::Types::String, null: true,
|
field :admin_url, GraphQL::Types::String, null: true,
|
||||||
description: 'Admin URL of the runner. Only available for adminstrators.'
|
description: 'Admin URL of the runner. Only available for administrators.'
|
||||||
|
|
||||||
def job_count
|
def job_count
|
||||||
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
|
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
|
||||||
|
|
|
@ -61,6 +61,11 @@ module ProfilesHelper
|
||||||
def ssh_key_expires_field_description
|
def ssh_key_expires_field_description
|
||||||
s_('Profiles|Key can still be used after expiration.')
|
s_('Profiles|Key can still be used after expiration.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Overridden in EE::ProfilesHelper#ssh_key_expiration_policy_enabled?
|
||||||
|
def ssh_key_expiration_policy_enabled?
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ProfilesHelper.prepend_mod
|
ProfilesHelper.prepend_mod
|
||||||
|
|
|
@ -44,7 +44,7 @@ module Ci
|
||||||
|
|
||||||
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
|
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
|
||||||
AVAILABLE_TYPES = runner_types.keys.freeze
|
AVAILABLE_TYPES = runner_types.keys.freeze
|
||||||
AVAILABLE_STATUSES = %w[active paused online offline not_connected].freeze
|
AVAILABLE_STATUSES = %w[active paused online offline not_connected stale].freeze
|
||||||
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
|
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
|
||||||
|
|
||||||
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
||||||
|
@ -287,10 +287,15 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def stale?
|
def stale?
|
||||||
|
return false unless created_at
|
||||||
|
|
||||||
[created_at, contacted_at].compact.max < self.class.stale_deadline
|
[created_at, contacted_at].compact.max < self.class.stale_deadline
|
||||||
end
|
end
|
||||||
|
|
||||||
def status
|
def status(legacy_mode = nil)
|
||||||
|
return deprecated_rest_status if legacy_mode == '14.5'
|
||||||
|
|
||||||
|
return :stale if stale?
|
||||||
return :not_connected unless contacted_at
|
return :not_connected unless contacted_at
|
||||||
|
|
||||||
online? ? :online : :offline
|
online? ? :online : :offline
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
= render_if_exists 'admin/application_settings/git_two_factor_session_expiry', form: f
|
= render_if_exists 'admin/application_settings/git_two_factor_session_expiry', form: f
|
||||||
= render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f
|
= render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f
|
||||||
= render_if_exists 'admin/application_settings/enforce_pat_expiration', form: f
|
= render_if_exists 'admin/application_settings/enforce_pat_expiration', form: f
|
||||||
|
= render_if_exists 'admin/application_settings/ssh_key_expiration_policy', form: f
|
||||||
= render_if_exists 'admin/application_settings/enforce_ssh_key_expiration', form: f
|
= render_if_exists 'admin/application_settings/enforce_ssh_key_expiration', form: f
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
- max_date = ::Gitlab::CurrentSettings.max_ssh_key_lifetime_from_now.to_date if ssh_key_expiration_policy_enabled?
|
||||||
%div
|
%div
|
||||||
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
|
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
|
||||||
= form_errors(@key)
|
= form_errors(@key)
|
||||||
|
@ -13,8 +14,8 @@
|
||||||
%p.form-text.text-muted= s_('Profiles|Give your individual key a title. This will be publicly visible.')
|
%p.form-text.text-muted= s_('Profiles|Give your individual key a title. This will be publicly visible.')
|
||||||
|
|
||||||
.col.form-group
|
.col.form-group
|
||||||
= f.label :expires_at, s_('Profiles|Expires at'), class: 'label-bold'
|
= f.label :expires_at, s_('Profiles|Expiration date'), class: 'label-bold'
|
||||||
= f.date_field :expires_at, class: "form-control input-lg", min: Date.tomorrow, data: { qa_selector: 'key_expiry_date_field' }
|
= f.date_field :expires_at, class: "form-control input-lg", min: Date.tomorrow, max: max_date, data: { qa_selector: 'key_expiry_date_field' }
|
||||||
%p.form-text.text-muted{ data: { qa_selector: 'key_expiry_date_field_description' } }= ssh_key_expires_field_description
|
%p.form-text.text-muted{ data: { qa_selector: 'key_expiry_date_field_description' } }= ssh_key_expires_field_description
|
||||||
|
|
||||||
.js-add-ssh-key-validation-warning.hide
|
.js-add-ssh-key-validation-warning.hide
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddMaxSshKeyLifetimeToApplicationSettings < Gitlab::Database::Migration[1.0]
|
||||||
|
def change
|
||||||
|
add_column :application_settings, :max_ssh_key_lifetime, :integer
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
7686fd3e33b25b811aba459aba514cde8e88102277edb3be7e12378cb7e8de85
|
|
@ -10477,6 +10477,7 @@ CREATE TABLE application_settings (
|
||||||
sentry_dsn text,
|
sentry_dsn text,
|
||||||
sentry_clientside_dsn text,
|
sentry_clientside_dsn text,
|
||||||
sentry_environment text,
|
sentry_environment text,
|
||||||
|
max_ssh_key_lifetime integer,
|
||||||
static_objects_external_storage_auth_token_encrypted text,
|
static_objects_external_storage_auth_token_encrypted text,
|
||||||
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
|
||||||
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
|
||||||
|
|
|
@ -8750,7 +8750,7 @@ Represents the total number of issues and their weights for a particular day.
|
||||||
| ---- | ---- | ----------- |
|
| ---- | ---- | ----------- |
|
||||||
| <a id="cirunneraccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel!`](#cirunneraccesslevel) | Access level of the runner. |
|
| <a id="cirunneraccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel!`](#cirunneraccesslevel) | Access level of the runner. |
|
||||||
| <a id="cirunneractive"></a>`active` | [`Boolean!`](#boolean) | Indicates the runner is allowed to receive jobs. |
|
| <a id="cirunneractive"></a>`active` | [`Boolean!`](#boolean) | Indicates the runner is allowed to receive jobs. |
|
||||||
| <a id="cirunneradminurl"></a>`adminUrl` | [`String`](#string) | Admin URL of the runner. Only available for adminstrators. |
|
| <a id="cirunneradminurl"></a>`adminUrl` | [`String`](#string) | Admin URL of the runner. Only available for administrators. |
|
||||||
| <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Last contact from the runner. |
|
| <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Last contact from the runner. |
|
||||||
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
|
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
|
||||||
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
|
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
|
||||||
|
@ -8765,11 +8765,24 @@ Represents the total number of issues and their weights for a particular day.
|
||||||
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
|
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
|
||||||
| <a id="cirunnerrunnertype"></a>`runnerType` | [`CiRunnerType!`](#cirunnertype) | Type of the runner. |
|
| <a id="cirunnerrunnertype"></a>`runnerType` | [`CiRunnerType!`](#cirunnertype) | Type of the runner. |
|
||||||
| <a id="cirunnershortsha"></a>`shortSha` | [`String`](#string) | First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID. |
|
| <a id="cirunnershortsha"></a>`shortSha` | [`String`](#string) | First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID. |
|
||||||
| <a id="cirunnerstatus"></a>`status` | [`CiRunnerStatus!`](#cirunnerstatus) | Status of the runner. |
|
|
||||||
| <a id="cirunnertaglist"></a>`tagList` | [`[String!]`](#string) | Tags associated with the runner. |
|
| <a id="cirunnertaglist"></a>`tagList` | [`[String!]`](#string) | Tags associated with the runner. |
|
||||||
| <a id="cirunneruserpermissions"></a>`userPermissions` | [`RunnerPermissions!`](#runnerpermissions) | Permissions for the current user on the resource. |
|
| <a id="cirunneruserpermissions"></a>`userPermissions` | [`RunnerPermissions!`](#runnerpermissions) | Permissions for the current user on the resource. |
|
||||||
| <a id="cirunnerversion"></a>`version` | [`String`](#string) | Version of the runner. |
|
| <a id="cirunnerversion"></a>`version` | [`String`](#string) | Version of the runner. |
|
||||||
|
|
||||||
|
#### Fields with arguments
|
||||||
|
|
||||||
|
##### `CiRunner.status`
|
||||||
|
|
||||||
|
Status of the runner.
|
||||||
|
|
||||||
|
Returns [`CiRunnerStatus!`](#cirunnerstatus).
|
||||||
|
|
||||||
|
###### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="cirunnerstatuslegacymode"></a>`legacyMode` **{warning-solid}** | [`String`](#string) | **Deprecated** in 14.6. Will be removed in 15.0. From that release onward, the field will behave as if legacyMode is null. |
|
||||||
|
|
||||||
### `CiStage`
|
### `CiStage`
|
||||||
|
|
||||||
#### Fields
|
#### Fields
|
||||||
|
@ -15956,11 +15969,12 @@ Values for sorting runners.
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
| ----- | ----------- |
|
| ----- | ----------- |
|
||||||
| <a id="cirunnerstatusactive"></a>`ACTIVE` | A runner that is not paused. |
|
| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
|
||||||
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` | A runner that has never contacted this instance. |
|
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period of no contact. |
|
||||||
| <a id="cirunnerstatusoffline"></a>`OFFLINE` | A runner that has not contacted this instance within the last 2 hours. |
|
| <a id="cirunnerstatusoffline"></a>`OFFLINE` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period offline. |
|
||||||
| <a id="cirunnerstatusonline"></a>`ONLINE` | A runner that contacted this instance within the last 2 hours. |
|
| <a id="cirunnerstatusonline"></a>`ONLINE` | Runner that contacted this instance within the last 2 hours. |
|
||||||
| <a id="cirunnerstatuspaused"></a>`PAUSED` | A runner that is paused. |
|
| <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
|
||||||
|
| <a id="cirunnerstatusstale"></a>`STALE` | Runner that has not contacted this instance within the last 3 months. Only available if legacyMode is null. Will be a possible return value starting in 15.0. |
|
||||||
|
|
||||||
### `CiRunnerType`
|
### `CiRunnerType`
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,11 @@ type: reference, api
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
|
||||||
|
|
||||||
WARNING:
|
WARNING:
|
||||||
This API is in an alpha stage and considered unstable.
|
This API is in the process of being deprecated and considered unstable.
|
||||||
The response payload may be subject to change or breakage
|
The response payload may be subject to change or breakage
|
||||||
across GitLab releases.
|
across GitLab releases. Please use the
|
||||||
|
[GraphQL API](graphql/reference/index.md#queryvulnerabilities)
|
||||||
|
instead.
|
||||||
|
|
||||||
Every API call to vulnerabilities must be [authenticated](index.md#authentication).
|
Every API call to vulnerabilities must be [authenticated](index.md#authentication).
|
||||||
|
|
||||||
|
|
|
@ -343,6 +343,7 @@ listed in the descriptions of the relevant settings.
|
||||||
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited) [Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to 0 in GitLab 13.8. |
|
| `max_import_size` | integer | no | Maximum import size in MB. 0 for unlimited. Default = 0 (unlimited) [Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to 0 in GitLab 13.8. |
|
||||||
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB. |
|
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB. |
|
||||||
| `max_personal_access_token_lifetime` | integer | no | **(ULTIMATE SELF)** Maximum allowable lifetime for personal access tokens in days. |
|
| `max_personal_access_token_lifetime` | integer | no | **(ULTIMATE SELF)** Maximum allowable lifetime for personal access tokens in days. |
|
||||||
|
| `max_ssh_key_lifetime` | integer | no | **(ULTIMATE SELF)** Maximum allowable lifetime for SSH keys in days. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1007) in GitLab 14.6. |
|
||||||
| `metrics_method_call_threshold` | integer | no | A method call is only tracked when it takes longer than the given amount of milliseconds. |
|
| `metrics_method_call_threshold` | integer | no | A method call is only tracked when it takes longer than the given amount of milliseconds. |
|
||||||
| `mirror_available` | boolean | no | Allow repository mirroring to configured by project Maintainers. If disabled, only Administrators can configure repository mirroring. |
|
| `mirror_available` | boolean | no | Allow repository mirroring to configured by project Maintainers. If disabled, only Administrators can configure repository mirroring. |
|
||||||
| `mirror_capacity_threshold` | integer | no | **(PREMIUM)** Minimum capacity to be available before scheduling more mirrors preemptively. |
|
| `mirror_capacity_threshold` | integer | no | **(PREMIUM)** Minimum capacity to be available before scheduling more mirrors preemptively. |
|
||||||
|
|
|
@ -192,6 +192,62 @@ To set a limit on how long these sessions are valid:
|
||||||
1. Fill in the **Session duration for Git operations when 2FA is enabled (minutes)** field.
|
1. Fill in the **Session duration for Git operations when 2FA is enabled (minutes)** field.
|
||||||
1. Click **Save changes**.
|
1. Click **Save changes**.
|
||||||
|
|
||||||
|
## Limit the lifetime of SSH keys **(ULTIMATE SELF)**
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1007) in GitLab 14.6 [with a flag](../../../administration/feature_flags.md) named `ff_limit_ssh_key_lifetime`. Disabled by default.
|
||||||
|
|
||||||
|
FLAG:
|
||||||
|
On self-managed GitLab, by default this feature is not available. To make it available,
|
||||||
|
ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `ff_limit_ssh_key_lifetime`.
|
||||||
|
On GitLab.com, this feature is not available. The feature is not ready for production use.
|
||||||
|
|
||||||
|
Users can optionally specify a lifetime for
|
||||||
|
[SSH keys](../../../ssh/index.md).
|
||||||
|
This lifetime is not a requirement, and can be set to any arbitrary number of days.
|
||||||
|
|
||||||
|
SSH keys are user credentials to access GitLab.
|
||||||
|
However, organizations with security requirements may want to enforce more protection by
|
||||||
|
requiring the regular rotation of these keys.
|
||||||
|
|
||||||
|
### Set a lifetime
|
||||||
|
|
||||||
|
Only a GitLab administrator can set a lifetime. Leaving it empty means
|
||||||
|
there are no restrictions.
|
||||||
|
|
||||||
|
To set a lifetime on how long SSH keys are valid:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Admin**.
|
||||||
|
1. On the left sidebar, select **Settings > General**.
|
||||||
|
1. Expand the **Account and limit** section.
|
||||||
|
1. Fill in the **Maximum allowable lifetime for SSH keys (days)** field.
|
||||||
|
1. Click **Save changes**.
|
||||||
|
|
||||||
|
Once a lifetime for SSH keys is set, GitLab:
|
||||||
|
|
||||||
|
- Requires users to set an expiration date that is no later than the allowed lifetime on new
|
||||||
|
SSH keys.
|
||||||
|
- Applies the lifetime restriction to existing SSH keys. Keys with no expiry or a lifetime
|
||||||
|
greater than the maximum immediately become invalid.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
When a user's SSH key becomes invalid they can delete and re-add the same key again.
|
||||||
|
|
||||||
|
## Allow expired SSH keys to be used **(ULTIMATE SELF)**
|
||||||
|
|
||||||
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250480) in GitLab 13.9.
|
||||||
|
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/320970) in GitLab 14.0.
|
||||||
|
|
||||||
|
By default, expired SSH keys **are not usable**.
|
||||||
|
|
||||||
|
To allow the use of expired SSH keys:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Admin**.
|
||||||
|
1. On the left sidebar, select **Settings > General**.
|
||||||
|
1. Expand the **Account and limit** section.
|
||||||
|
1. Uncheck the **Enforce SSH key expiration** checkbox.
|
||||||
|
|
||||||
|
Disabling SSH key expiration immediately enables all expired SSH keys.
|
||||||
|
|
||||||
## Limit the lifetime of personal access tokens **(ULTIMATE SELF)**
|
## Limit the lifetime of personal access tokens **(ULTIMATE SELF)**
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3649) in GitLab 12.6.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3649) in GitLab 12.6.
|
||||||
|
@ -225,22 +281,6 @@ Once a lifetime for personal access tokens is set, GitLab:
|
||||||
allowed lifetime. Three hours is given to allow administrators to change the allowed lifetime,
|
allowed lifetime. Three hours is given to allow administrators to change the allowed lifetime,
|
||||||
or remove it, before revocation takes place.
|
or remove it, before revocation takes place.
|
||||||
|
|
||||||
## Allow expired SSH keys to be used **(ULTIMATE SELF)**
|
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250480) in GitLab 13.9.
|
|
||||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/320970) in GitLab 14.0.
|
|
||||||
|
|
||||||
By default, expired SSH keys **are not usable**.
|
|
||||||
|
|
||||||
To allow the use of expired SSH keys:
|
|
||||||
|
|
||||||
1. On the top bar, select **Menu > Admin**.
|
|
||||||
1. On the left sidebar, select **Settings > General**.
|
|
||||||
1. Expand the **Account and limit** section.
|
|
||||||
1. Uncheck the **Enforce SSH key expiration** checkbox.
|
|
||||||
|
|
||||||
Disabling SSH key expiration immediately enables all expired SSH keys.
|
|
||||||
|
|
||||||
## Allow expired Personal Access Tokens to be used **(ULTIMATE SELF)**
|
## Allow expired Personal Access Tokens to be used **(ULTIMATE SELF)**
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214723) in GitLab 13.1.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214723) in GitLab 13.1.
|
||||||
|
|
|
@ -21496,6 +21496,9 @@ msgstr ""
|
||||||
msgid "Maximum allowable lifetime for personal access token (days)"
|
msgid "Maximum allowable lifetime for personal access token (days)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Maximum allowed lifetime for SSH keys (in days)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Maximum artifacts size"
|
msgid "Maximum artifacts size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26705,15 +26708,15 @@ msgstr ""
|
||||||
msgid "Profiles|Enter your pronouns to let people know how to refer to you"
|
msgid "Profiles|Enter your pronouns to let people know how to refer to you"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Profiles|Expiration date"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Expired key is not valid."
|
msgid "Profiles|Expired key is not valid."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Expired:"
|
msgid "Profiles|Expired:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Expires at"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Profiles|Expires:"
|
msgid "Profiles|Expires:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26753,15 +26756,18 @@ msgstr ""
|
||||||
msgid "Profiles|Key"
|
msgid "Profiles|Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Profiles|Key becomes invalid on this date."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Profiles|Key becomes invalid on this date. Maximum lifetime for SSH keys is %{max_ssh_key_lifetime} days"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Key can still be used after expiration."
|
msgid "Profiles|Key can still be used after expiration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Key usable beyond expiration date."
|
msgid "Profiles|Key usable beyond expiration date."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Profiles|Key will be deleted on this date."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Profiles|Last used:"
|
msgid "Profiles|Last used:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -39221,6 +39227,9 @@ msgstr ""
|
||||||
msgid "When a runner is locked, it cannot be assigned to other projects"
|
msgid "When a runner is locked, it cannot be assigned to other projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "When enabled, SSH keys with no expiry date or an invalid expiration date are no longer accepted. Leave blank for no limit."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "When enabled, existing personal access tokens may be revoked. Leave blank for no limit."
|
msgid "When enabled, existing personal access tokens may be revoked. Leave blank for no limit."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import { mapValues } from 'lodash';
|
||||||
import App from '~/google_cloud/components/app.vue';
|
import App from '~/google_cloud/components/app.vue';
|
||||||
import Home from '~/google_cloud/components/home.vue';
|
import Home from '~/google_cloud/components/home.vue';
|
||||||
import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
|
import IncubationBanner from '~/google_cloud/components/incubation_banner.vue';
|
||||||
|
@ -8,103 +9,59 @@ import NoGcpProjects from '~/google_cloud/components/errors/no_gcp_projects.vue'
|
||||||
|
|
||||||
const BASE_FEEDBACK_URL =
|
const BASE_FEEDBACK_URL =
|
||||||
'https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new';
|
'https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/meta/-/issues/new';
|
||||||
|
const SCREEN_COMPONENTS = {
|
||||||
|
Home,
|
||||||
|
ServiceAccountsForm,
|
||||||
|
GcpError,
|
||||||
|
NoGcpProjects,
|
||||||
|
};
|
||||||
|
const SERVICE_ACCOUNTS_FORM_PROPS = {
|
||||||
|
gcpProjects: [1, 2, 3],
|
||||||
|
environments: [4, 5, 6],
|
||||||
|
cancelPath: '',
|
||||||
|
};
|
||||||
|
const HOME_PROPS = {
|
||||||
|
serviceAccounts: [{}, {}],
|
||||||
|
createServiceAccountUrl: '#url-create-service-account',
|
||||||
|
emptyIllustrationUrl: '#url-empty-illustration',
|
||||||
|
};
|
||||||
|
|
||||||
describe('google_cloud App component', () => {
|
describe('google_cloud App component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
|
const findIncubationBanner = () => wrapper.findComponent(IncubationBanner);
|
||||||
const findGcpError = () => wrapper.findComponent(GcpError);
|
|
||||||
const findNoGcpProjects = () => wrapper.findComponent(NoGcpProjects);
|
|
||||||
const findServiceAccountsForm = () => wrapper.findComponent(ServiceAccountsForm);
|
|
||||||
const findHome = () => wrapper.findComponent(Home);
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('for gcp_error screen', () => {
|
describe.each`
|
||||||
|
screen | extraProps | componentName
|
||||||
|
${'gcp_error'} | ${{ error: 'mock_gcp_client_error' }} | ${'GcpError'}
|
||||||
|
${'no_gcp_projects'} | ${{}} | ${'NoGcpProjects'}
|
||||||
|
${'service_accounts_form'} | ${SERVICE_ACCOUNTS_FORM_PROPS} | ${'ServiceAccountsForm'}
|
||||||
|
${'home'} | ${HOME_PROPS} | ${'Home'}
|
||||||
|
`('for screen=$screen', ({ screen, extraProps, componentName }) => {
|
||||||
|
const component = SCREEN_COMPONENTS[componentName];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const propsData = {
|
wrapper = shallowMount(App, { propsData: { screen, ...extraProps } });
|
||||||
screen: 'gcp_error',
|
|
||||||
error: 'mock_gcp_client_error',
|
|
||||||
};
|
|
||||||
wrapper = shallowMount(App, { propsData });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the gcp_error screen', () => {
|
it(`renders only ${componentName}`, () => {
|
||||||
expect(findGcpError().exists()).toBe(true);
|
const existences = mapValues(SCREEN_COMPONENTS, (x) => wrapper.findComponent(x).exists());
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain incubation banner', () => {
|
expect(existences).toEqual({
|
||||||
expect(findIncubationBanner().props()).toEqual({
|
...mapValues(SCREEN_COMPONENTS, () => false),
|
||||||
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
[componentName]: true,
|
||||||
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
|
||||||
featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('for no_gcp_projects screen', () => {
|
it(`renders the ${componentName} with props`, () => {
|
||||||
beforeEach(() => {
|
expect(wrapper.findComponent(component).props()).toEqual(extraProps);
|
||||||
const propsData = {
|
|
||||||
screen: 'no_gcp_projects',
|
|
||||||
};
|
|
||||||
wrapper = shallowMount(App, { propsData });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the no_gcp_projects screen', () => {
|
it('renders incubation banner', () => {
|
||||||
expect(findNoGcpProjects().exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain incubation banner', () => {
|
|
||||||
expect(findIncubationBanner().props()).toEqual({
|
|
||||||
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
|
||||||
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
|
||||||
featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for service_accounts_form screen', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const propsData = {
|
|
||||||
screen: 'service_accounts_form',
|
|
||||||
gcpProjects: [1, 2, 3],
|
|
||||||
environments: [4, 5, 6],
|
|
||||||
cancelPath: '',
|
|
||||||
};
|
|
||||||
wrapper = shallowMount(App, { propsData });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the service_accounts_form screen', () => {
|
|
||||||
expect(findServiceAccountsForm().exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain incubation banner', () => {
|
|
||||||
expect(findIncubationBanner().props()).toEqual({
|
|
||||||
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
|
||||||
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
|
||||||
featureRequestUrl: `${BASE_FEEDBACK_URL}?issuable_template=feature_request`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('for home screen', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const propsData = {
|
|
||||||
screen: 'home',
|
|
||||||
serviceAccounts: [{}, {}],
|
|
||||||
createServiceAccountUrl: '#url-create-service-account',
|
|
||||||
emptyIllustrationUrl: '#url-empty-illustration',
|
|
||||||
};
|
|
||||||
wrapper = shallowMount(App, { propsData });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the home screen', () => {
|
|
||||||
expect(findHome().exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain incubation banner', () => {
|
|
||||||
expect(findIncubationBanner().props()).toEqual({
|
expect(findIncubationBanner().props()).toEqual({
|
||||||
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
||||||
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
||||||
|
|
|
@ -145,13 +145,20 @@ describe('Sidebar date Widget', () => {
|
||||||
${false} | ${SidebarInheritDate} | ${'SidebarInheritDate'} | ${false}
|
${false} | ${SidebarInheritDate} | ${'SidebarInheritDate'} | ${false}
|
||||||
`(
|
`(
|
||||||
'when canInherit is $canInherit, $componentName display is $expected',
|
'when canInherit is $canInherit, $componentName display is $expected',
|
||||||
({ canInherit, component, expected }) => {
|
async ({ canInherit, component, expected }) => {
|
||||||
createComponent({ canInherit });
|
createComponent({ canInherit });
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
expect(wrapper.find(component).exists()).toBe(expected);
|
expect(wrapper.find(component).exists()).toBe(expected);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it('does not render SidebarInheritDate when canInherit is true and date is loading', async () => {
|
||||||
|
createComponent({ canInherit: true });
|
||||||
|
|
||||||
|
expect(wrapper.find(SidebarInheritDate).exists()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('displays a flash message when query is rejected', async () => {
|
it('displays a flash message when query is rejected', async () => {
|
||||||
createComponent({
|
createComponent({
|
||||||
dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
|
dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
|
||||||
|
|
|
@ -10,7 +10,7 @@ describe('SidebarInheritDate', () => {
|
||||||
const findFixedRadio = () => wrapper.findAll(GlFormRadio).at(0);
|
const findFixedRadio = () => wrapper.findAll(GlFormRadio).at(0);
|
||||||
const findInheritRadio = () => wrapper.findAll(GlFormRadio).at(1);
|
const findInheritRadio = () => wrapper.findAll(GlFormRadio).at(1);
|
||||||
|
|
||||||
const createComponent = () => {
|
const createComponent = ({ dueDateIsFixed = false } = {}) => {
|
||||||
wrapper = shallowMount(SidebarInheritDate, {
|
wrapper = shallowMount(SidebarInheritDate, {
|
||||||
provide: {
|
provide: {
|
||||||
canUpdate: true,
|
canUpdate: true,
|
||||||
|
@ -18,11 +18,10 @@ describe('SidebarInheritDate', () => {
|
||||||
propsData: {
|
propsData: {
|
||||||
issuable: {
|
issuable: {
|
||||||
dueDate: '2021-04-15',
|
dueDate: '2021-04-15',
|
||||||
dueDateIsFixed: true,
|
dueDateIsFixed,
|
||||||
dueDateFixed: '2021-04-15',
|
dueDateFixed: '2021-04-15',
|
||||||
dueDateFromMilestones: '2021-05-15',
|
dueDateFromMilestones: '2021-05-15',
|
||||||
},
|
},
|
||||||
isLoading: false,
|
|
||||||
dateType: 'dueDate',
|
dateType: 'dueDate',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -45,6 +44,13 @@ describe('SidebarInheritDate', () => {
|
||||||
expect(findInheritRadio().text()).toBe('Inherited:');
|
expect(findInheritRadio().text()).toBe('Inherited:');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not emit set-date if fixed value does not change', () => {
|
||||||
|
createComponent({ dueDateIsFixed: true });
|
||||||
|
findFixedRadio().vm.$emit('input', true);
|
||||||
|
|
||||||
|
expect(wrapper.emitted('set-date')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('emits set-date event on click on radio button', () => {
|
it('emits set-date event on click on radio button', () => {
|
||||||
findFixedRadio().vm.$emit('input', true);
|
findFixedRadio().vm.$emit('input', true);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Resolvers::Ci::RunnerStatusResolver do
|
||||||
|
include GraphqlHelpers
|
||||||
|
|
||||||
|
describe '#resolve' do
|
||||||
|
let(:user) { build(:user) }
|
||||||
|
let(:runner) { build(:ci_runner) }
|
||||||
|
|
||||||
|
subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, obj: runner, args: args) }
|
||||||
|
|
||||||
|
context 'with legacy_mode' do
|
||||||
|
context 'set to 14.5' do
|
||||||
|
let(:args) do
|
||||||
|
{ legacy_mode: '14.5' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls runner.status with specified legacy_mode' do
|
||||||
|
expect(runner).to receive(:status).with('14.5').once.and_return(:online)
|
||||||
|
|
||||||
|
expect(resolve_subject).to eq(:online)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'set to nil' do
|
||||||
|
let(:args) do
|
||||||
|
{ legacy_mode: nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls runner.status with specified legacy_mode' do
|
||||||
|
expect(runner).to receive(:status).with(nil).once.and_return(:stale)
|
||||||
|
|
||||||
|
expect(resolve_subject).to eq(:stale)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -342,6 +342,7 @@ RSpec.describe Ci::Runner do
|
||||||
using RSpec::Parameterized::TableSyntax
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
where(:created_at, :contacted_at, :expected_stale?) do
|
where(:created_at, :contacted_at, :expected_stale?) do
|
||||||
|
nil | nil | false
|
||||||
3.months.ago - 1.second | 3.months.ago - 0.001.seconds | true
|
3.months.ago - 1.second | 3.months.ago - 0.001.seconds | true
|
||||||
3.months.ago - 1.second | 3.months.ago + 1.hour | false
|
3.months.ago - 1.second | 3.months.ago + 1.hour | false
|
||||||
3.months.ago - 1.second | nil | true
|
3.months.ago - 1.second | nil | true
|
||||||
|
@ -376,6 +377,8 @@ RSpec.describe Ci::Runner do
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_redis_runner_contacted_at(value)
|
def stub_redis_runner_contacted_at(value)
|
||||||
|
return unless created_at
|
||||||
|
|
||||||
Gitlab::Redis::Cache.with do |redis|
|
Gitlab::Redis::Cache.with do |redis|
|
||||||
cache_key = runner.send(:cache_attribute_key)
|
cache_key = runner.send(:cache_attribute_key)
|
||||||
expect(redis).to receive(:get).with(cache_key)
|
expect(redis).to receive(:get).with(cache_key)
|
||||||
|
@ -419,7 +422,7 @@ RSpec.describe Ci::Runner do
|
||||||
it { is_expected.to be_falsey }
|
it { is_expected.to be_falsey }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'contacted long time ago time' do
|
context 'contacted long time ago' do
|
||||||
before do
|
before do
|
||||||
runner.contacted_at = 1.year.ago
|
runner.contacted_at = 1.year.ago
|
||||||
end
|
end
|
||||||
|
@ -437,7 +440,7 @@ RSpec.describe Ci::Runner do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with cache value' do
|
context 'with cache value' do
|
||||||
context 'contacted long time ago time' do
|
context 'contacted long time ago' do
|
||||||
before do
|
before do
|
||||||
runner.contacted_at = 1.year.ago
|
runner.contacted_at = 1.year.ago
|
||||||
stub_redis_runner_contacted_at(1.year.ago.to_s)
|
stub_redis_runner_contacted_at(1.year.ago.to_s)
|
||||||
|
@ -699,16 +702,33 @@ RSpec.describe Ci::Runner do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#status' do
|
describe '#status' do
|
||||||
let(:runner) { build(:ci_runner, :instance) }
|
let(:runner) { build(:ci_runner, :instance, created_at: 4.months.ago) }
|
||||||
|
let(:legacy_mode) { }
|
||||||
|
|
||||||
subject { runner.status }
|
subject { runner.status(legacy_mode) }
|
||||||
|
|
||||||
context 'never connected' do
|
context 'never connected' do
|
||||||
before do
|
before do
|
||||||
runner.contacted_at = nil
|
runner.contacted_at = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(:not_connected) }
|
context 'with legacy_mode enabled' do
|
||||||
|
let(:legacy_mode) { '14.5' }
|
||||||
|
|
||||||
|
it { is_expected.to eq(:not_connected) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with legacy_mode disabled' do
|
||||||
|
it { is_expected.to eq(:stale) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'created recently' do
|
||||||
|
before do
|
||||||
|
runner.created_at = 1.day.ago
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to eq(:not_connected) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'inactive but online' do
|
context 'inactive but online' do
|
||||||
|
@ -717,7 +737,15 @@ RSpec.describe Ci::Runner do
|
||||||
runner.active = false
|
runner.active = false
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(:online) }
|
context 'with legacy_mode enabled' do
|
||||||
|
let(:legacy_mode) { '14.5' }
|
||||||
|
|
||||||
|
it { is_expected.to eq(:paused) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with legacy_mode disabled' do
|
||||||
|
it { is_expected.to eq(:online) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'contacted 1s ago' do
|
context 'contacted 1s ago' do
|
||||||
|
@ -728,13 +756,29 @@ RSpec.describe Ci::Runner do
|
||||||
it { is_expected.to eq(:online) }
|
it { is_expected.to eq(:online) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'contacted long time ago' do
|
context 'contacted recently' do
|
||||||
before do
|
before do
|
||||||
runner.contacted_at = 1.year.ago
|
runner.contacted_at = (3.months - 1.hour).ago
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(:offline) }
|
it { is_expected.to eq(:offline) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'contacted long time ago' do
|
||||||
|
before do
|
||||||
|
runner.contacted_at = (3.months + 1.second).ago
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with legacy_mode enabled' do
|
||||||
|
let(:legacy_mode) { '14.5' }
|
||||||
|
|
||||||
|
it { is_expected.to eq(:offline) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with legacy_mode disabled' do
|
||||||
|
it { is_expected.to eq(:stale) }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#deprecated_rest_status' do
|
describe '#deprecated_rest_status' do
|
||||||
|
|
|
@ -63,7 +63,7 @@ RSpec.describe 'Query.runner(id)' do
|
||||||
'revision' => runner.revision,
|
'revision' => runner.revision,
|
||||||
'locked' => false,
|
'locked' => false,
|
||||||
'active' => runner.active,
|
'active' => runner.active,
|
||||||
'status' => runner.status.to_s.upcase,
|
'status' => runner.status('14.5').to_s.upcase,
|
||||||
'maximumTimeout' => runner.maximum_timeout,
|
'maximumTimeout' => runner.maximum_timeout,
|
||||||
'accessLevel' => runner.access_level.to_s.upcase,
|
'accessLevel' => runner.access_level.to_s.upcase,
|
||||||
'runUntagged' => runner.run_untagged,
|
'runUntagged' => runner.run_untagged,
|
||||||
|
@ -221,6 +221,45 @@ RSpec.describe 'Query.runner(id)' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'for runner with status' do
|
||||||
|
let_it_be(:stale_runner) { create(:ci_runner, description: 'Stale runner 1', created_at: 3.months.ago) }
|
||||||
|
|
||||||
|
let(:query) do
|
||||||
|
%(
|
||||||
|
query {
|
||||||
|
staleRunner: runner(id: "#{stale_runner.to_global_id}") {
|
||||||
|
status
|
||||||
|
legacyStatusWithExplicitVersion: status(legacyMode: "14.5")
|
||||||
|
newStatus: status(legacyMode: null)
|
||||||
|
}
|
||||||
|
pausedRunner: runner(id: "#{inactive_instance_runner.to_global_id}") {
|
||||||
|
status
|
||||||
|
legacyStatusWithExplicitVersion: status(legacyMode: "14.5")
|
||||||
|
newStatus: status(legacyMode: null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'retrieves status fields with expected values' do
|
||||||
|
post_graphql(query, current_user: user)
|
||||||
|
|
||||||
|
stale_runner_data = graphql_data_at(:stale_runner)
|
||||||
|
expect(stale_runner_data).to match a_hash_including(
|
||||||
|
'status' => 'NOT_CONNECTED',
|
||||||
|
'legacyStatusWithExplicitVersion' => 'NOT_CONNECTED',
|
||||||
|
'newStatus' => 'STALE'
|
||||||
|
)
|
||||||
|
|
||||||
|
paused_runner_data = graphql_data_at(:paused_runner)
|
||||||
|
expect(paused_runner_data).to match a_hash_including(
|
||||||
|
'status' => 'PAUSED',
|
||||||
|
'legacyStatusWithExplicitVersion' => 'PAUSED',
|
||||||
|
'newStatus' => 'OFFLINE'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'for multiple runners' do
|
describe 'for multiple runners' do
|
||||||
let_it_be(:project1) { create(:project, :test_repo) }
|
let_it_be(:project1) { create(:project, :test_repo) }
|
||||||
let_it_be(:project2) { create(:project, :test_repo) }
|
let_it_be(:project2) { create(:project, :test_repo) }
|
||||||
|
|
|
@ -33,8 +33,8 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the expires at field', :aggregate_failures do
|
it 'has the expires at field', :aggregate_failures do
|
||||||
expect(rendered).to have_field('Expires at', type: 'date')
|
expect(rendered).to have_field('Expiration date', type: 'date')
|
||||||
expect(page.find_field('Expires at')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d"))
|
expect(page.find_field('Expiration date')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d"))
|
||||||
expect(rendered).to have_text('Key can still be used after expiration.')
|
expect(rendered).to have_text('Key can still be used after expiration.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue