Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
fde9b7a784
commit
1b9b475faa
|
@ -124,6 +124,9 @@ export default {
|
|||
isLoading() {
|
||||
return this.$apollo.queries.issuable.loading || this.loading;
|
||||
},
|
||||
initialLoading() {
|
||||
return this.$apollo.queries.issuable.loading;
|
||||
},
|
||||
hasDate() {
|
||||
return this.dateValue !== null;
|
||||
},
|
||||
|
@ -271,10 +274,10 @@ export default {
|
|||
<span class="collapse-truncated-title">{{ formattedDate }}</span>
|
||||
</div>
|
||||
<sidebar-inherit-date
|
||||
v-if="canInherit"
|
||||
v-if="canInherit && !initialLoading"
|
||||
:issuable="issuable"
|
||||
:is-loading="isLoading"
|
||||
:date-type="dateType"
|
||||
:is-loading="isLoading"
|
||||
@reset-date="setDate(null)"
|
||||
@set-date="setFixedDate"
|
||||
/>
|
||||
|
|
|
@ -17,8 +17,9 @@ export default {
|
|||
type: Object,
|
||||
},
|
||||
isLoading: {
|
||||
required: true,
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dateType: {
|
||||
type: String,
|
||||
|
@ -31,6 +32,7 @@ export default {
|
|||
return this.issuable?.[dateFields[this.dateType].isDateFixed] || false;
|
||||
},
|
||||
set(fixed) {
|
||||
if (fixed === this.issuable[dateFields[this.dateType].isDateFixed]) return;
|
||||
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
|
||||
graphql_name 'CiRunnerStatus'
|
||||
|
||||
::Ci::Runner::AVAILABLE_STATUSES.each do |status|
|
||||
description = case status
|
||||
when 'active'
|
||||
"A runner that is not paused."
|
||||
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 'ACTIVE',
|
||||
description: 'Runner that is not paused.',
|
||||
deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
|
||||
value: :active
|
||||
|
||||
value status.to_s.upcase,
|
||||
description: description,
|
||||
value: status.to_sym
|
||||
end
|
||||
value 'PAUSED',
|
||||
description: 'Runner that is paused.',
|
||||
deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
|
||||
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
|
||||
|
|
|
@ -27,8 +27,11 @@ module Types
|
|||
description: 'Access level of the runner.'
|
||||
field :active, GraphQL::Types::Boolean, null: false,
|
||||
description: 'Indicates the runner is allowed to receive jobs.'
|
||||
field :status, ::Types::Ci::RunnerStatusEnum, null: false,
|
||||
description: 'Status of the runner.'
|
||||
field :status,
|
||||
Types::Ci::RunnerStatusEnum,
|
||||
null: false,
|
||||
description: 'Status of the runner.',
|
||||
resolver: ::Resolvers::Ci::RunnerStatusResolver
|
||||
field :version, GraphQL::Types::String, null: true,
|
||||
description: 'Version of the runner.'
|
||||
field :short_sha, GraphQL::Types::String, null: true,
|
||||
|
@ -50,7 +53,7 @@ module Types
|
|||
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)."
|
||||
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
|
||||
# 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
|
||||
s_('Profiles|Key can still be used after expiration.')
|
||||
end
|
||||
|
||||
# Overridden in EE::ProfilesHelper#ssh_key_expiration_policy_enabled?
|
||||
def ssh_key_expiration_policy_enabled?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
ProfilesHelper.prepend_mod
|
||||
|
|
|
@ -44,7 +44,7 @@ module Ci
|
|||
|
||||
AVAILABLE_TYPES_LEGACY = %w[specific shared].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
|
||||
|
||||
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
|
||||
|
@ -287,10 +287,15 @@ module Ci
|
|||
end
|
||||
|
||||
def stale?
|
||||
return false unless created_at
|
||||
|
||||
[created_at, contacted_at].compact.max < self.class.stale_deadline
|
||||
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
|
||||
|
||||
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/personal_access_token_expiration_policy', 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
|
||||
|
||||
.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
|
||||
= form_for [:profile, @key], html: { class: 'js-requires-input' } do |f|
|
||||
= 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.')
|
||||
|
||||
.col.form-group
|
||||
= f.label :expires_at, s_('Profiles|Expires at'), class: 'label-bold'
|
||||
= f.date_field :expires_at, class: "form-control input-lg", min: Date.tomorrow, data: { qa_selector: 'key_expiry_date_field' }
|
||||
= f.label :expires_at, s_('Profiles|Expiration date'), class: 'label-bold'
|
||||
= 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
|
||||
|
||||
.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_clientside_dsn text,
|
||||
sentry_environment text,
|
||||
max_ssh_key_lifetime integer,
|
||||
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_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="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="cirunnerdescription"></a>`description` | [`String`](#string) | Description 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="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="cirunnerstatus"></a>`status` | [`CiRunnerStatus!`](#cirunnerstatus) | Status of 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="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`
|
||||
|
||||
#### Fields
|
||||
|
@ -15956,11 +15969,12 @@ Values for sorting runners.
|
|||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="cirunnerstatusactive"></a>`ACTIVE` | A runner that is not paused. |
|
||||
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` | A runner that has never contacted this instance. |
|
||||
| <a id="cirunnerstatusoffline"></a>`OFFLINE` | A runner that has not contacted this instance within the last 2 hours. |
|
||||
| <a id="cirunnerstatusonline"></a>`ONLINE` | A runner that contacted this instance within the last 2 hours. |
|
||||
| <a id="cirunnerstatuspaused"></a>`PAUSED` | A runner that is paused. |
|
||||
| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
|
||||
| <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` **{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` | Runner that contacted this instance within the last 2 hours. |
|
||||
| <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`
|
||||
|
||||
|
|
|
@ -10,9 +10,11 @@ type: reference, api
|
|||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10242) in GitLab 12.6.
|
||||
|
||||
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
|
||||
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).
|
||||
|
||||
|
|
|
@ -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_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_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. |
|
||||
| `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. |
|
||||
|
|
|
@ -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. 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)**
|
||||
|
||||
> [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,
|
||||
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)**
|
||||
|
||||
> - [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)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum allowed lifetime for SSH keys (in days)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Maximum artifacts size"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26705,15 +26708,15 @@ msgstr ""
|
|||
msgid "Profiles|Enter your pronouns to let people know how to refer to you"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Expiration date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Expired key is not valid."
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Expired:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Expires at"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Expires:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26753,15 +26756,18 @@ msgstr ""
|
|||
msgid "Profiles|Key"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Key usable beyond expiration date."
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Key will be deleted on this date."
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Last used:"
|
||||
msgstr ""
|
||||
|
||||
|
@ -39221,6 +39227,9 @@ msgstr ""
|
|||
msgid "When a runner is locked, it cannot be assigned to other projects"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mapValues } from 'lodash';
|
||||
import App from '~/google_cloud/components/app.vue';
|
||||
import Home from '~/google_cloud/components/home.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 =
|
||||
'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', () => {
|
||||
let wrapper;
|
||||
|
||||
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(() => {
|
||||
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(() => {
|
||||
const propsData = {
|
||||
screen: 'gcp_error',
|
||||
error: 'mock_gcp_client_error',
|
||||
};
|
||||
wrapper = shallowMount(App, { propsData });
|
||||
wrapper = shallowMount(App, { propsData: { screen, ...extraProps } });
|
||||
});
|
||||
|
||||
it('renders the gcp_error screen', () => {
|
||||
expect(findGcpError().exists()).toBe(true);
|
||||
});
|
||||
it(`renders only ${componentName}`, () => {
|
||||
const existences = mapValues(SCREEN_COMPONENTS, (x) => wrapper.findComponent(x).exists());
|
||||
|
||||
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`,
|
||||
expect(existences).toEqual({
|
||||
...mapValues(SCREEN_COMPONENTS, () => false),
|
||||
[componentName]: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('for no_gcp_projects screen', () => {
|
||||
beforeEach(() => {
|
||||
const propsData = {
|
||||
screen: 'no_gcp_projects',
|
||||
};
|
||||
wrapper = shallowMount(App, { propsData });
|
||||
it(`renders the ${componentName} with props`, () => {
|
||||
expect(wrapper.findComponent(component).props()).toEqual(extraProps);
|
||||
});
|
||||
|
||||
it('renders the no_gcp_projects screen', () => {
|
||||
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', () => {
|
||||
it('renders incubation banner', () => {
|
||||
expect(findIncubationBanner().props()).toEqual({
|
||||
shareFeedbackUrl: `${BASE_FEEDBACK_URL}?issuable_template=general_feedback`,
|
||||
reportBugUrl: `${BASE_FEEDBACK_URL}?issuable_template=report_bug`,
|
||||
|
|
|
@ -145,13 +145,20 @@ describe('Sidebar date Widget', () => {
|
|||
${false} | ${SidebarInheritDate} | ${'SidebarInheritDate'} | ${false}
|
||||
`(
|
||||
'when canInherit is $canInherit, $componentName display is $expected',
|
||||
({ canInherit, component, expected }) => {
|
||||
async ({ canInherit, component, expected }) => {
|
||||
createComponent({ canInherit });
|
||||
await waitForPromises();
|
||||
|
||||
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 () => {
|
||||
createComponent({
|
||||
dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
|
||||
|
|
|
@ -10,7 +10,7 @@ describe('SidebarInheritDate', () => {
|
|||
const findFixedRadio = () => wrapper.findAll(GlFormRadio).at(0);
|
||||
const findInheritRadio = () => wrapper.findAll(GlFormRadio).at(1);
|
||||
|
||||
const createComponent = () => {
|
||||
const createComponent = ({ dueDateIsFixed = false } = {}) => {
|
||||
wrapper = shallowMount(SidebarInheritDate, {
|
||||
provide: {
|
||||
canUpdate: true,
|
||||
|
@ -18,11 +18,10 @@ describe('SidebarInheritDate', () => {
|
|||
propsData: {
|
||||
issuable: {
|
||||
dueDate: '2021-04-15',
|
||||
dueDateIsFixed: true,
|
||||
dueDateIsFixed,
|
||||
dueDateFixed: '2021-04-15',
|
||||
dueDateFromMilestones: '2021-05-15',
|
||||
},
|
||||
isLoading: false,
|
||||
dateType: 'dueDate',
|
||||
},
|
||||
});
|
||||
|
@ -45,6 +44,13 @@ describe('SidebarInheritDate', () => {
|
|||
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', () => {
|
||||
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
|
||||
|
||||
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 + 1.hour | false
|
||||
3.months.ago - 1.second | nil | true
|
||||
|
@ -376,6 +377,8 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
|
||||
def stub_redis_runner_contacted_at(value)
|
||||
return unless created_at
|
||||
|
||||
Gitlab::Redis::Cache.with do |redis|
|
||||
cache_key = runner.send(:cache_attribute_key)
|
||||
expect(redis).to receive(:get).with(cache_key)
|
||||
|
@ -419,7 +422,7 @@ RSpec.describe Ci::Runner do
|
|||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'contacted long time ago time' do
|
||||
context 'contacted long time ago' do
|
||||
before do
|
||||
runner.contacted_at = 1.year.ago
|
||||
end
|
||||
|
@ -437,7 +440,7 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
|
||||
context 'with cache value' do
|
||||
context 'contacted long time ago time' do
|
||||
context 'contacted long time ago' do
|
||||
before do
|
||||
runner.contacted_at = 1.year.ago
|
||||
stub_redis_runner_contacted_at(1.year.ago.to_s)
|
||||
|
@ -699,16 +702,33 @@ RSpec.describe Ci::Runner do
|
|||
end
|
||||
|
||||
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
|
||||
before do
|
||||
runner.contacted_at = nil
|
||||
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
|
||||
|
||||
context 'inactive but online' do
|
||||
|
@ -717,7 +737,15 @@ RSpec.describe Ci::Runner do
|
|||
runner.active = false
|
||||
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
|
||||
|
||||
context 'contacted 1s ago' do
|
||||
|
@ -728,13 +756,29 @@ RSpec.describe Ci::Runner do
|
|||
it { is_expected.to eq(:online) }
|
||||
end
|
||||
|
||||
context 'contacted long time ago' do
|
||||
context 'contacted recently' do
|
||||
before do
|
||||
runner.contacted_at = 1.year.ago
|
||||
runner.contacted_at = (3.months - 1.hour).ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:offline) }
|
||||
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
|
||||
|
||||
describe '#deprecated_rest_status' do
|
||||
|
|
|
@ -63,7 +63,7 @@ RSpec.describe 'Query.runner(id)' do
|
|||
'revision' => runner.revision,
|
||||
'locked' => false,
|
||||
'active' => runner.active,
|
||||
'status' => runner.status.to_s.upcase,
|
||||
'status' => runner.status('14.5').to_s.upcase,
|
||||
'maximumTimeout' => runner.maximum_timeout,
|
||||
'accessLevel' => runner.access_level.to_s.upcase,
|
||||
'runUntagged' => runner.run_untagged,
|
||||
|
@ -221,6 +221,45 @@ RSpec.describe 'Query.runner(id)' do
|
|||
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
|
||||
let_it_be(:project1) { 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
|
||||
|
||||
it 'has the expires at field', :aggregate_failures do
|
||||
expect(rendered).to have_field('Expires at', type: 'date')
|
||||
expect(page.find_field('Expires at')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d"))
|
||||
expect(rendered).to have_field('Expiration date', type: 'date')
|
||||
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.')
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue