Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5958e399de
commit
61d62a2960
|
@ -20,12 +20,8 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
|
||||||
|
|
||||||
def revoke
|
def revoke
|
||||||
@personal_access_token = finder.find(params[:id])
|
@personal_access_token = finder.find(params[:id])
|
||||||
|
service = PersonalAccessTokens::RevokeService.new(current_user, token: @personal_access_token).execute
|
||||||
if @personal_access_token.revoke!
|
service.success? ? flash[:notice] = service.message : flash[:alert] = service.message
|
||||||
flash[:notice] = _("Revoked personal access token %{personal_access_token_name}!") % { personal_access_token_name: @personal_access_token.name }
|
|
||||||
else
|
|
||||||
flash[:alert] = _("Could not revoke personal access token %{personal_access_token_name}.") % { personal_access_token_name: @personal_access_token.name }
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to profile_personal_access_tokens_path
|
redirect_to profile_personal_access_tokens_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -194,6 +194,10 @@ module ApplicationHelper
|
||||||
'https://' + promo_host
|
'https://' + promo_host
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def contact_sales_url
|
||||||
|
promo_url + '/sales'
|
||||||
|
end
|
||||||
|
|
||||||
def support_url
|
def support_url
|
||||||
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
|
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
|
||||||
end
|
end
|
||||||
|
|
|
@ -284,7 +284,7 @@ module Ci
|
||||||
def expire_in=(value)
|
def expire_in=(value)
|
||||||
self.expire_at =
|
self.expire_at =
|
||||||
if value
|
if value
|
||||||
ChronicDuration.parse(value)&.seconds&.from_now
|
::Gitlab::Ci::Build::Artifacts::ExpireInParser.new(value).seconds_from_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
class PersonalAccessTokenPolicy < BasePolicy
|
class PersonalAccessTokenPolicy < BasePolicy
|
||||||
condition(:is_owner) { user && subject.user_id == user.id }
|
condition(:is_owner) { user && subject.user_id == user.id }
|
||||||
|
|
||||||
rule { is_owner | admin }.policy do
|
rule { is_owner | admin & ~blocked }.policy do
|
||||||
enable :read_token
|
enable :read_token
|
||||||
|
enable :revoke_token
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Metrics
|
||||||
DASHBOARD_NAME = N_('K8s pod health')
|
DASHBOARD_NAME = N_('K8s pod health')
|
||||||
|
|
||||||
# SHA256 hash of dashboard content
|
# SHA256 hash of dashboard content
|
||||||
DASHBOARD_VERSION = '0515db7a99078a2423b037f99251ba16bd163603c0a30229ae8aa7386e96421c'
|
DASHBOARD_VERSION = '3a91b32f91b2dd3d90275333c0ea3630b3f3f37c4296ede5b5eef59bf523d66b'
|
||||||
|
|
||||||
SEQUENCE = [
|
SEQUENCE = [
|
||||||
STAGES::MetricEndpointInserter,
|
STAGES::MetricEndpointInserter,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PersonalAccessTokens
|
||||||
|
class RevokeService
|
||||||
|
attr_reader :token, :current_user
|
||||||
|
|
||||||
|
def initialize(current_user = nil, params = { token: nil })
|
||||||
|
@current_user = current_user
|
||||||
|
@token = params[:token]
|
||||||
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
return ServiceResponse.error(message: 'Not permitted to revoke') unless revocation_permitted?
|
||||||
|
|
||||||
|
if token.revoke!
|
||||||
|
ServiceResponse.success(message: success_message)
|
||||||
|
else
|
||||||
|
ServiceResponse.error(message: error_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def error_message
|
||||||
|
_("Could not revoke personal access token %{personal_access_token_name}.") % { personal_access_token_name: token.name }
|
||||||
|
end
|
||||||
|
|
||||||
|
def success_message
|
||||||
|
_("Revoked personal access token %{personal_access_token_name}!") % { personal_access_token_name: token.name }
|
||||||
|
end
|
||||||
|
|
||||||
|
def revocation_permitted?
|
||||||
|
Ability.allowed?(current_user, :revoke_token, token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Replace deprecated button on vulnerability details page
|
||||||
|
merge_request: 38679
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add support for never keyword in expire_in job artifacts
|
||||||
|
merge_request: 38578
|
||||||
|
author: Fabio Huser
|
||||||
|
type: added
|
|
@ -15,55 +15,101 @@ panel_groups:
|
||||||
panels:
|
panels:
|
||||||
- title: "CPU usage"
|
- title: "CPU usage"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Cores per pod"
|
y_label: "Cores per container"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_cpu_usage_seconds_total
|
- id: pod_cpu_usage_seconds_total
|
||||||
query_range: 'rate(container_cpu_usage_seconds_total{pod="{{pod}}",container="POD"}[5m])'
|
query_range: >-
|
||||||
|
sum(
|
||||||
|
rate(container_cpu_usage_seconds_total{pod="{{pod}}",container!="POD"}[5m])
|
||||||
|
)
|
||||||
|
by (container)
|
||||||
unit: "cores"
|
unit: "cores"
|
||||||
label: pod
|
label: container
|
||||||
|
|
||||||
|
- title: "CPU throttling"
|
||||||
|
type: "line-chart"
|
||||||
|
y_label: "Cores per container"
|
||||||
|
metrics:
|
||||||
|
- id: pod_cpu_cfs_throttle
|
||||||
|
query_range: >-
|
||||||
|
sum(
|
||||||
|
rate(container_cpu_cfs_throttled_seconds_total{pod="{{pod}}"}[5m])
|
||||||
|
)
|
||||||
|
by (container)
|
||||||
|
unit: "cores"
|
||||||
|
label: container
|
||||||
|
|
||||||
- group: Memory metrics
|
- group: Memory metrics
|
||||||
panels:
|
panels:
|
||||||
- title: "Memory usage working set"
|
- title: "Memory usage working set"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Working set memory (MiB)"
|
y_label: "Working set memory"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_memory_working_set
|
- id: pod_memory_working_set
|
||||||
query_range: 'container_memory_working_set_bytes{pod="{{pod}}",container="POD"}/1024/1024'
|
query_range: >-
|
||||||
unit: "MiB"
|
sum(
|
||||||
label: pod
|
container_memory_working_set_bytes{pod="{{pod}}",container!="POD"}
|
||||||
|
) by (container)
|
||||||
|
unit: "bytes"
|
||||||
|
label: container
|
||||||
|
|
||||||
- group: Network metrics
|
- group: Network metrics
|
||||||
panels:
|
panels:
|
||||||
- title: "Network Receive (In)"
|
- title: "Network Receive (In)"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Received (KiB/sec)"
|
y_label: "Received (bytes/sec)"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_network_receive
|
- id: pod_network_receive
|
||||||
query_range: 'rate(container_network_receive_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
|
query_range: >-
|
||||||
unit: "KiB / sec"
|
sum(
|
||||||
|
rate(
|
||||||
|
container_network_receive_bytes_total{pod="{{pod}}"}[5m]
|
||||||
|
)
|
||||||
|
) by (pod)
|
||||||
|
unit: "bytes"
|
||||||
label: pod
|
label: pod
|
||||||
|
|
||||||
- title: "Network Transmit (Out)"
|
- title: "Network Transmit (Out)"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Transmitted (KiB/sec)"
|
y_label: "Transmitted (bytes/sec)"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_network_transmit
|
- id: pod_network_transmit
|
||||||
query_range: 'rate(container_network_transmit_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
|
query_range: >-
|
||||||
unit: "KiB / sec"
|
sum(
|
||||||
|
rate(
|
||||||
|
container_network_transmit_bytes_total{pod="{{pod}}"}[5m]
|
||||||
|
)
|
||||||
|
) by (pod)
|
||||||
|
unit: bytes
|
||||||
label: pod
|
label: pod
|
||||||
|
|
||||||
- group: Disk metrics
|
- group: Disk metrics
|
||||||
panels:
|
panels:
|
||||||
- title: "Disk Reads"
|
- title: "Disk Reads"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Disk reads (KiB/sec)"
|
y_label: "Disk reads (bytes/sec)"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_disk_reads
|
- id: pod_disk_reads
|
||||||
query_range: 'rate(container_fs_reads_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
|
query_range: >-
|
||||||
unit: "KiB / sec"
|
sum(
|
||||||
label: pod
|
rate(
|
||||||
|
container_fs_reads_bytes_total{pod="{{pod}}", container!="POD"}[5m]
|
||||||
|
)
|
||||||
|
) by (container,device)
|
||||||
|
|
||||||
|
unit: "bytes / sec"
|
||||||
|
label: "{{container}} {{device}}"
|
||||||
|
|
||||||
- title: "Disk Writes"
|
- title: "Disk Writes"
|
||||||
type: "line-chart"
|
type: "line-chart"
|
||||||
y_label: "Disk writes (KiB/sec)"
|
y_label: "Disk writes (bytes/sec)"
|
||||||
metrics:
|
metrics:
|
||||||
- id: pod_disk_writes
|
- id: pod_disk_writes
|
||||||
query_range: 'rate(container_fs_writes_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
|
query_range: >-
|
||||||
unit: "KiB / sec"
|
sum(
|
||||||
label: pod
|
rate(
|
||||||
|
container_fs_writes_bytes_total{pod="{{pod}}", container!="POD"}[5m]
|
||||||
|
)
|
||||||
|
) by (container,device)
|
||||||
|
unit: "bytes / sec"
|
||||||
|
label: "{{container}} {{device}}"
|
||||||
|
|
|
@ -194,14 +194,13 @@ keys must be manually replicated to the **secondary** node.
|
||||||
1. Visit the **primary** node's **Admin Area > Geo**
|
1. Visit the **primary** node's **Admin Area > Geo**
|
||||||
(`/admin/geo/nodes`) in your browser.
|
(`/admin/geo/nodes`) in your browser.
|
||||||
1. Click the **New node** button.
|
1. Click the **New node** button.
|
||||||
![Add secondary node](img/adding_a_secondary_node.png)
|
![Add secondary node](img/adding_a_secondary_node_v13_3.png)
|
||||||
1. Fill in **Name** with the `gitlab_rails['geo_node_name']` in
|
1. Fill in **Name** with the `gitlab_rails['geo_node_name']` in
|
||||||
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
|
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
|
||||||
for character.
|
for character.
|
||||||
1. Fill in **URL** with the `external_url` in `/etc/gitlab/gitlab.rb`. These
|
1. Fill in **URL** with the `external_url` in `/etc/gitlab/gitlab.rb`. These
|
||||||
values must always match, but it doesn't matter if one ends with a `/` and
|
values must always match, but it doesn't matter if one ends with a `/` and
|
||||||
the other doesn't.
|
the other doesn't.
|
||||||
1. **Do NOT** check the **This is a primary node** checkbox.
|
|
||||||
1. Optionally, choose which groups or storage shards should be replicated by the
|
1. Optionally, choose which groups or storage shards should be replicated by the
|
||||||
**secondary** node. Leave blank to replicate all. Read more in
|
**secondary** node. Leave blank to replicate all. Read more in
|
||||||
[selective synchronization](#selective-synchronization).
|
[selective synchronization](#selective-synchronization).
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
|
@ -204,6 +204,7 @@ control over how the Pages daemon runs and serves content in your environment.
|
||||||
| `external_https` | Configure Pages to bind to one or more secondary IP addresses, serving HTTPS requests. Multiple addresses can be given as an array, along with exact ports, for example `['1.2.3.4', '1.2.3.5:8063']`. Sets value for `listen_https`.
|
| `external_https` | Configure Pages to bind to one or more secondary IP addresses, serving HTTPS requests. Multiple addresses can be given as an array, along with exact ports, for example `['1.2.3.4', '1.2.3.5:8063']`. Sets value for `listen_https`.
|
||||||
| `gitlab_client_http_timeout` | GitLab API HTTP client connection timeout in seconds (default: 10s).
|
| `gitlab_client_http_timeout` | GitLab API HTTP client connection timeout in seconds (default: 10s).
|
||||||
| `gitlab_client_jwt_expiry` | JWT Token expiry time in seconds (default: 30s).
|
| `gitlab_client_jwt_expiry` | JWT Token expiry time in seconds (default: 30s).
|
||||||
|
| `domain_config_source` | Domain configuration source (default: `disk`)
|
||||||
| `gitlab_id` | The OAuth application public ID. Leave blank to automatically fill when Pages authenticates with GitLab.
|
| `gitlab_id` | The OAuth application public ID. Leave blank to automatically fill when Pages authenticates with GitLab.
|
||||||
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab.
|
| `gitlab_secret` | The OAuth application secret. Leave blank to automatically fill when Pages authenticates with GitLab.
|
||||||
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`.
|
| `gitlab_server` | Server to use for authentication when access control is enabled; defaults to GitLab `external_url`.
|
||||||
|
@ -601,6 +602,43 @@ configuring a load balancer to work at the IP level, and so on. If you wish to
|
||||||
set up GitLab Pages on multiple servers, perform the above procedure for each
|
set up GitLab Pages on multiple servers, perform the above procedure for each
|
||||||
Pages server.
|
Pages server.
|
||||||
|
|
||||||
|
## Domain source configuration
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217912) in GitLab 13.3.
|
||||||
|
|
||||||
|
GitLab Pages can use different sources to get domain configuration.
|
||||||
|
The default value is `nil`; however, GitLab Pages will default to `disk`.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gitlab_pages['domain_config_source'] = nil
|
||||||
|
```
|
||||||
|
|
||||||
|
You can specify `gitlab` to enable [API-based configuration](#gitlab-api-based-configuration).
|
||||||
|
|
||||||
|
For more details see this [blog post](https://about.gitlab.com/blog/2020/08/03/how-gitlab-pages-uses-the-gitlab-api-to-serve-content/).
|
||||||
|
|
||||||
|
### GitLab API-based configuration
|
||||||
|
|
||||||
|
GitLab Pages can use an API-based configuration. This replaces disk source configuration, which
|
||||||
|
was used prior to GitLab 13.0. Follow these steps to enable it:
|
||||||
|
|
||||||
|
1. Add the following to your `/etc/gitlab/gitlab.erb` file:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gitlab_pages['domain_config_source'] = "gitlab"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||||
|
|
||||||
|
If you encounter an issue, you can disable it by choosing `disk` or `nil`:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
gitlab_pages['domain_config_source'] = nil
|
||||||
|
```
|
||||||
|
|
||||||
|
For other common issues, see the [troubleshooting section](#failed-to-connect-to-the-internal-gitlab-api)
|
||||||
|
or report an issue.
|
||||||
|
|
||||||
## Backup
|
## Backup
|
||||||
|
|
||||||
GitLab Pages are part of the [regular backup](../../raketasks/backup_restore.md), so there is no separate backup to configure.
|
GitLab Pages are part of the [regular backup](../../raketasks/backup_restore.md), so there is no separate backup to configure.
|
||||||
|
@ -696,3 +734,24 @@ date > /var/opt/gitlab/gitlab-rails/shared/pages/.update
|
||||||
```
|
```
|
||||||
|
|
||||||
If you've customized the Pages storage path, adjust the command above to use your custom path.
|
If you've customized the Pages storage path, adjust the command above to use your custom path.
|
||||||
|
|
||||||
|
### Failed to connect to the internal GitLab API
|
||||||
|
|
||||||
|
If you have enabled [API-based configuration](#gitlab-api-based-configuration) and see the following error:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
ERRO[0010] Failed to connect to the internal GitLab API after 0.50s error="failed to connect to internal Pages API: HTTP status: 401"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are [Running GitLab Pages on a separate server](#running-gitlab-pages-on-a-separate-server)
|
||||||
|
you must copy the `/etc/gitlab/gitlab-secrets.json` file
|
||||||
|
from the **GitLab server** to the **Pages server** after upgrading to GitLab 13.3,
|
||||||
|
as described in that section.
|
||||||
|
|
||||||
|
Other reasons may include network connectivity issues between your
|
||||||
|
**GitLab server** and your **Pages server** such as firewall configurations or closed ports.
|
||||||
|
For example, if there is a connection timeout:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
error="failed to connect to internal Pages API: Get \"https://gitlab.example.com:3000/api/v4/internal/pages/status\": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)"
|
||||||
|
```
|
||||||
|
|
|
@ -3184,8 +3184,11 @@ stored on GitLab. If the expiry time is not defined, it defaults to the
|
||||||
[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only)
|
[instance wide setting](../../user/admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only)
|
||||||
(30 days by default).
|
(30 days by default).
|
||||||
|
|
||||||
You can use the **Keep** button on the job page to override expiration and
|
To override the expiration time and keep artifacts forever:
|
||||||
keep artifacts forever.
|
|
||||||
|
- Use the **Keep** button on the job page.
|
||||||
|
- Set the value of `expire_in` to `never`. [Available](https://gitlab.com/gitlab-org/gitlab/-/issues/22761)
|
||||||
|
in GitLab 13.3 and later.
|
||||||
|
|
||||||
After their expiry, artifacts are deleted hourly by default (via a cron job),
|
After their expiry, artifacts are deleted hourly by default (via a cron job),
|
||||||
and are not accessible anymore.
|
and are not accessible anymore.
|
||||||
|
@ -3200,6 +3203,7 @@ provided. Examples of valid values:
|
||||||
- `6 mos 1 day`
|
- `6 mos 1 day`
|
||||||
- `47 yrs 6 mos and 4d`
|
- `47 yrs 6 mos and 4d`
|
||||||
- `3 weeks and 2 days`
|
- `3 weeks and 2 days`
|
||||||
|
- `never`
|
||||||
|
|
||||||
To expire artifacts 1 week after being uploaded:
|
To expire artifacts 1 week after being uploaded:
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ To create and add a new Kubernetes cluster to your project, group, or instance:
|
||||||
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an EKS management IAM role.
|
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an EKS management IAM role.
|
||||||
To do so, follow the [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) instructions
|
To do so, follow the [Amazon EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) instructions
|
||||||
to create a IAM role suitable for managing the AWS EKS cluster's resources on your behalf.
|
to create a IAM role suitable for managing the AWS EKS cluster's resources on your behalf.
|
||||||
In addition to the policies that guide suggests, you must also include the `AmazonEKSServicePolicy`
|
In addition to the policies that guide suggests, you must also include the `AmazonEKSClusterPolicy`
|
||||||
policy for this role in order for GitLab to manage the EKS cluster correctly.
|
policy for this role in order for GitLab to manage the EKS cluster correctly.
|
||||||
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an IAM role:
|
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an IAM role:
|
||||||
1. From the left panel, select **Roles**.
|
1. From the left panel, select **Roles**.
|
||||||
|
@ -208,7 +208,7 @@ NOTE: **Note:**
|
||||||
This role should be the role you created by following the
|
This role should be the role you created by following the
|
||||||
[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) guide.
|
[EKS cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) guide.
|
||||||
In addition to the policies that guide suggests, you must also include the
|
In addition to the policies that guide suggests, you must also include the
|
||||||
`AmazonEKSServicePolicy` policy for this role in order for GitLab to manage the EKS cluster correctly.
|
`AmazonEKSClusterPolicy` policy for this role in order for GitLab to manage the EKS cluster correctly.
|
||||||
|
|
||||||
## Existing EKS cluster
|
## Existing EKS cluster
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Build
|
||||||
|
module Artifacts
|
||||||
|
class ExpireInParser
|
||||||
|
def self.validate_duration(value)
|
||||||
|
new(value).validate_duration
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(value)
|
||||||
|
@value = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_duration
|
||||||
|
return true if never?
|
||||||
|
|
||||||
|
parse
|
||||||
|
rescue ChronicDuration::DurationParseError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def seconds_from_now
|
||||||
|
parse&.seconds&.from_now
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_reader :value
|
||||||
|
|
||||||
|
def parse
|
||||||
|
return if never?
|
||||||
|
|
||||||
|
ChronicDuration.parse(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def never?
|
||||||
|
value.to_s.casecmp('never') == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,7 +42,7 @@ module Gitlab
|
||||||
inclusion: { in: %w[on_success on_failure always],
|
inclusion: { in: %w[on_success on_failure always],
|
||||||
message: 'should be on_success, on_failure ' \
|
message: 'should be on_success, on_failure ' \
|
||||||
'or always' }
|
'or always' }
|
||||||
validates :expire_in, duration: true
|
validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::Artifacts::ExpireInParser }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,17 +6,27 @@ module Gitlab
|
||||||
module LegacyValidationHelpers
|
module LegacyValidationHelpers
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_duration(value)
|
def validate_duration(value, parser = nil)
|
||||||
value.is_a?(String) && ChronicDuration.parse(value)
|
return false unless value.is_a?(String)
|
||||||
|
|
||||||
|
if parser && parser.respond_to?(:validate_duration)
|
||||||
|
parser.validate_duration(value)
|
||||||
|
else
|
||||||
|
ChronicDuration.parse(value)
|
||||||
|
end
|
||||||
rescue ChronicDuration::DurationParseError
|
rescue ChronicDuration::DurationParseError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_duration_limit(value, limit)
|
def validate_duration_limit(value, limit, parser = nil)
|
||||||
return false unless value.is_a?(String)
|
return false unless value.is_a?(String)
|
||||||
|
|
||||||
ChronicDuration.parse(value).second.from_now <
|
if parser && parser.respond_to?(:validate_duration_limit)
|
||||||
ChronicDuration.parse(limit).second.from_now
|
parser.validate_duration_limit(value, limit)
|
||||||
|
else
|
||||||
|
ChronicDuration.parse(value).second.from_now <
|
||||||
|
ChronicDuration.parse(limit).second.from_now
|
||||||
|
end
|
||||||
rescue ChronicDuration::DurationParseError
|
rescue ChronicDuration::DurationParseError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -106,12 +106,12 @@ module Gitlab
|
||||||
include LegacyValidationHelpers
|
include LegacyValidationHelpers
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
unless validate_duration(value)
|
unless validate_duration(value, options[:parser])
|
||||||
record.errors.add(attribute, 'should be a duration')
|
record.errors.add(attribute, 'should be a duration')
|
||||||
end
|
end
|
||||||
|
|
||||||
if options[:limit]
|
if options[:limit]
|
||||||
unless validate_duration_limit(value, options[:limit])
|
unless validate_duration_limit(value, options[:limit], options[:parser])
|
||||||
record.errors.add(attribute, 'should not exceed the limit')
|
record.errors.add(attribute, 'should not exceed the limit')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,6 +56,9 @@ module Gitlab
|
||||||
},
|
},
|
||||||
terms_opt_in: {
|
terms_opt_in: {
|
||||||
tracking_category: 'Growth::Acquisition::Experiment::TermsOptIn'
|
tracking_category: 'Growth::Acquisition::Experiment::TermsOptIn'
|
||||||
|
},
|
||||||
|
contact_sales_btn_in_app: {
|
||||||
|
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp'
|
||||||
}
|
}
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
|
|
@ -3824,6 +3824,9 @@ msgstr ""
|
||||||
msgid "BillingPlans|per user"
|
msgid "BillingPlans|per user"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "BillingPlan|Contact sales"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "BillingPlan|Upgrade"
|
msgid "BillingPlan|Upgrade"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -21303,6 +21306,9 @@ msgstr ""
|
||||||
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
|
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "SecurityConfiguration|Create Merge Request"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "SecurityConfiguration|Customize common SAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{linkStart}GitLab SAST documentation%{linkEnd}."
|
msgid "SecurityConfiguration|Customize common SAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{linkStart}GitLab SAST documentation%{linkEnd}."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -100,14 +100,11 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
|
||||||
context "when revocation fails" do
|
context "when revocation fails" do
|
||||||
it "displays an error message" do
|
it "displays an error message" do
|
||||||
visit profile_personal_access_tokens_path
|
visit profile_personal_access_tokens_path
|
||||||
allow_any_instance_of(PersonalAccessToken).to receive(:update!).and_return(false)
|
allow_any_instance_of(PersonalAccessTokens::RevokeService).to receive(:revocation_permitted?).and_return(false)
|
||||||
|
|
||||||
errors = ActiveModel::Errors.new(PersonalAccessToken.new).tap { |e| e.add(:name, "cannot be nil") }
|
|
||||||
allow_any_instance_of(PersonalAccessToken).to receive(:errors).and_return(errors)
|
|
||||||
|
|
||||||
accept_confirm { click_on "Revoke" }
|
accept_confirm { click_on "Revoke" }
|
||||||
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
|
expect(active_personal_access_tokens).to have_text(personal_access_token.name)
|
||||||
expect(page).to have_content("Could not revoke")
|
expect(page).to have_content("Not permitted to revoke")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -168,6 +168,19 @@ RSpec.describe ApplicationHelper do
|
||||||
it { expect(helper.active_when(false)).to eq(nil) }
|
it { expect(helper.active_when(false)).to eq(nil) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#contact_sales_url' do
|
||||||
|
subject { helper.contact_sales_url }
|
||||||
|
|
||||||
|
it 'passes a smoke test' do
|
||||||
|
is_expected.to eq('https://about.gitlab.com/sales')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'changes if promo_url changes' do
|
||||||
|
allow(helper).to receive(:promo_url).and_return('https://somewhere.else')
|
||||||
|
is_expected.to eq('https://somewhere.else/sales')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#support_url' do
|
describe '#support_url' do
|
||||||
context 'when alternate support url is specified' do
|
context 'when alternate support url is specified' do
|
||||||
let(:alternate_url) { 'http://company.example.com/getting-help' }
|
let(:alternate_url) { 'http://company.example.com/getting-help' }
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gitlab::Ci::Build::Artifacts::ExpireInParser do
|
||||||
|
describe '.validate_duration' do
|
||||||
|
subject { described_class.validate_duration(value) }
|
||||||
|
|
||||||
|
context 'with never' do
|
||||||
|
let(:value) { 'never' }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with never value camelized' do
|
||||||
|
let(:value) { 'Never' }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a duration' do
|
||||||
|
let(:value) { '1 Day' }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without a duration' do
|
||||||
|
let(:value) { 'something' }
|
||||||
|
|
||||||
|
it { is_expected.to be_falsy }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#seconds_from_now' do
|
||||||
|
subject { described_class.new(value).seconds_from_now }
|
||||||
|
|
||||||
|
context 'with never' do
|
||||||
|
let(:value) { 'never' }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an empty string' do
|
||||||
|
let(:value) { '' }
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a duration' do
|
||||||
|
let(:value) { '1 day' }
|
||||||
|
|
||||||
|
it { is_expected.to be_like_time(1.day.from_now) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1559,6 +1559,21 @@ module Gitlab
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns artifacts with expire_in never keyword" do
|
||||||
|
config = YAML.dump({
|
||||||
|
rspec: {
|
||||||
|
script: "rspec",
|
||||||
|
artifacts: { paths: ["releases/"], expire_in: "never" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
config_processor = Gitlab::Ci::YamlProcessor.new(config)
|
||||||
|
builds = config_processor.stage_builds_attributes("test")
|
||||||
|
|
||||||
|
expect(builds.size).to eq(1)
|
||||||
|
expect(builds.first[:options][:artifacts][:expire_in]).to eq('never')
|
||||||
|
end
|
||||||
|
|
||||||
%w[on_success on_failure always].each do |when_state|
|
%w[on_success on_failure always].each do |when_state|
|
||||||
it "returns artifacts for when #{when_state} defined" do
|
it "returns artifacts for when #{when_state} defined" do
|
||||||
config = YAML.dump({
|
config = YAML.dump({
|
||||||
|
|
|
@ -14,7 +14,7 @@ RSpec.describe PersonalAccessTokenPolicy do
|
||||||
end
|
end
|
||||||
|
|
||||||
with_them do
|
with_them do
|
||||||
context 'determine if a token is readable by a user' do
|
context 'determine if a token is readable or revocable by a user' do
|
||||||
let(:user) { build_stubbed(user_type) }
|
let(:user) { build_stubbed(user_type) }
|
||||||
let(:token_owner) { owned_by_same_user ? user : build(:user) }
|
let(:token_owner) { owned_by_same_user ? user : build(:user) }
|
||||||
let(:token) { build(:personal_access_token, user: token_owner) }
|
let(:token) { build(:personal_access_token, user: token_owner) }
|
||||||
|
@ -26,6 +26,17 @@ RSpec.describe PersonalAccessTokenPolicy do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to(expected_permitted? ? be_allowed(:read_token) : be_disallowed(:read_token)) }
|
it { is_expected.to(expected_permitted? ? be_allowed(:read_token) : be_disallowed(:read_token)) }
|
||||||
|
it { is_expected.to(expected_permitted? ? be_allowed(:revoke_token) : be_disallowed(:revoke_token)) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'current_user is a blocked administrator', :enable_admin_mode do
|
||||||
|
subject { described_class.new(current_user, token) }
|
||||||
|
|
||||||
|
let(:current_user) { create(:user, :admin, :blocked) }
|
||||||
|
let(:token) { create(:personal_access_token) }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(:revoke_token) }
|
||||||
|
it { is_expected.to be_disallowed(:read_token) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -479,6 +479,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
expect(job.reload.artifacts_expire_at).to be_nil
|
expect(job.reload.artifacts_expire_at).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when value is never' do
|
||||||
|
let(:expire_in) { 'never' }
|
||||||
|
let(:default_artifacts_expire_in) { '5 days' }
|
||||||
|
|
||||||
|
it 'does not set expire_in' do
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(job.reload.artifacts_expire_at).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -73,7 +73,7 @@ RSpec.describe Ci::CreateJobArtifactsService do
|
||||||
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
|
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when expire_in params is set' do
|
context 'when expire_in params is set to a specific value' do
|
||||||
before do
|
before do
|
||||||
params.merge!('expire_in' => '2 hours')
|
params.merge!('expire_in' => '2 hours')
|
||||||
end
|
end
|
||||||
|
@ -89,6 +89,23 @@ RSpec.describe Ci::CreateJobArtifactsService do
|
||||||
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
|
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when expire_in params is set to `never`' do
|
||||||
|
before do
|
||||||
|
params.merge!('expire_in' => 'never')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets expiration date according to the parameter' do
|
||||||
|
expected_expire_at = nil
|
||||||
|
|
||||||
|
expect(subject).to be_truthy
|
||||||
|
archive_artifact, metadata_artifact = job.job_artifacts.last(2)
|
||||||
|
|
||||||
|
expect(job.artifacts_expire_at).to eq(expected_expire_at)
|
||||||
|
expect(archive_artifact.expire_at).to eq(expected_expire_at)
|
||||||
|
expect(metadata_artifact.expire_at).to eq(expected_expire_at)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe PersonalAccessTokens::RevokeService do
|
||||||
|
shared_examples_for 'a successfully revoked token' do
|
||||||
|
it { expect(subject.success?).to be true }
|
||||||
|
it { expect(service.token.revoked?).to be true }
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples_for 'an unsuccessfully revoked token' do
|
||||||
|
it { expect(subject.success?).to be false }
|
||||||
|
it { expect(service.token.revoked?).to be false }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#execute' do
|
||||||
|
subject { service.execute }
|
||||||
|
|
||||||
|
let(:service) { described_class.new(current_user, token: token) }
|
||||||
|
|
||||||
|
context 'when current_user is an administrator' do
|
||||||
|
let_it_be(:current_user) { create(:admin) }
|
||||||
|
let_it_be(:token) { create(:personal_access_token) }
|
||||||
|
|
||||||
|
it_behaves_like 'a successfully revoked token'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when current_user is not an administrator' do
|
||||||
|
let_it_be(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
context 'token belongs to a different user' do
|
||||||
|
let_it_be(:token) { create(:personal_access_token) }
|
||||||
|
|
||||||
|
it_behaves_like 'an unsuccessfully revoked token'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'token belongs to current_user' do
|
||||||
|
let_it_be(:token) { create(:personal_access_token, user: current_user) }
|
||||||
|
|
||||||
|
it_behaves_like 'a successfully revoked token'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue