提交 61d62a29 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 5958e399
......@@ -20,12 +20,8 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def revoke
@personal_access_token = finder.find(params[:id])
if @personal_access_token.revoke!
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
service = PersonalAccessTokens::RevokeService.new(current_user, token: @personal_access_token).execute
service.success? ? flash[:notice] = service.message : flash[:alert] = service.message
redirect_to profile_personal_access_tokens_path
end
......
......@@ -194,6 +194,10 @@ module ApplicationHelper
'https://' + promo_host
end
def contact_sales_url
promo_url + '/sales'
end
def support_url
Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url + '/getting-help/'
end
......
......@@ -284,7 +284,7 @@ module Ci
def expire_in=(value)
self.expire_at =
if value
ChronicDuration.parse(value)&.seconds&.from_now
::Gitlab::Ci::Build::Artifacts::ExpireInParser.new(value).seconds_from_now
end
end
......
......@@ -3,7 +3,8 @@
class PersonalAccessTokenPolicy < BasePolicy
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 :revoke_token
end
end
......@@ -7,7 +7,7 @@ module Metrics
DASHBOARD_NAME = N_('K8s pod health')
# SHA256 hash of dashboard content
DASHBOARD_VERSION = '0515db7a99078a2423b037f99251ba16bd163603c0a30229ae8aa7386e96421c'
DASHBOARD_VERSION = '3a91b32f91b2dd3d90275333c0ea3630b3f3f37c4296ede5b5eef59bf523d66b'
SEQUENCE = [
STAGES::MetricEndpointInserter,
......
# 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
---
title: Replace deprecated button on vulnerability details page
merge_request: 38679
author:
type: other
---
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:
- title: "CPU usage"
type: "line-chart"
y_label: "Cores per pod"
y_label: "Cores per container"
metrics:
- 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"
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
panels:
- title: "Memory usage working set"
type: "line-chart"
y_label: "Working set memory (MiB)"
y_label: "Working set memory"
metrics:
- id: pod_memory_working_set
query_range: 'container_memory_working_set_bytes{pod="{{pod}}",container="POD"}/1024/1024'
unit: "MiB"
label: pod
query_range: >-
sum(
container_memory_working_set_bytes{pod="{{pod}}",container!="POD"}
) by (container)
unit: "bytes"
label: container
- group: Network metrics
panels:
- title: "Network Receive (In)"
type: "line-chart"
y_label: "Received (KiB/sec)"
y_label: "Received (bytes/sec)"
metrics:
- id: pod_network_receive
query_range: 'rate(container_network_receive_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
unit: "KiB / sec"
query_range: >-
sum(
rate(
container_network_receive_bytes_total{pod="{{pod}}"}[5m]
)
) by (pod)
unit: "bytes"
label: pod
- title: "Network Transmit (Out)"
type: "line-chart"
y_label: "Transmitted (KiB/sec)"
y_label: "Transmitted (bytes/sec)"
metrics:
- id: pod_network_transmit
query_range: 'rate(container_network_transmit_bytes_total{pod="{{pod}}",container="POD"}[5m])/1024'
unit: "KiB / sec"
query_range: >-
sum(
rate(
container_network_transmit_bytes_total{pod="{{pod}}"}[5m]
)
) by (pod)
unit: bytes
label: pod
- group: Disk metrics
panels:
- title: "Disk Reads"
type: "line-chart"
y_label: "Disk reads (KiB/sec)"
y_label: "Disk reads (bytes/sec)"
metrics:
- id: pod_disk_reads
query_range: 'rate(container_fs_reads_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
unit: "KiB / sec"
label: pod
query_range: >-
sum(
rate(
container_fs_reads_bytes_total{pod="{{pod}}", container!="POD"}[5m]
)
) by (container,device)
unit: "bytes / sec"
label: "{{container}} {{device}}"
- title: "Disk Writes"
type: "line-chart"
y_label: "Disk writes (KiB/sec)"
y_label: "Disk writes (bytes/sec)"
metrics:
- id: pod_disk_writes
query_range: 'rate(container_fs_writes_bytes_total{container="POD",pod="{{pod}}"}[5m])/1024'
unit: "KiB / sec"
label: pod
query_range: >-
sum(
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**
(`/admin/geo/nodes`) in your browser.
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
`/etc/gitlab/gitlab.rb`. These values must always match *exactly*, character
for character.
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
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
**secondary** node. Leave blank to replicate all. Read more in
[selective synchronization](#selective-synchronization).
......
......@@ -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`.
| `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).
| `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_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`.
......@@ -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
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
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.
### 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)
(30 days by default).
You can use the **Keep** button on the job page to override expiration and
keep artifacts forever.
To override the expiration time and 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),
and are not accessible anymore.
......@@ -3200,6 +3203,7 @@ provided. Examples of valid values:
- `6 mos 1 day`
- `47 yrs 6 mos and 4d`
- `3 weeks and 2 days`
- `never`
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.
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.
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.
1. In the [IAM Management Console](https://console.aws.amazon.com/iam/home), create an IAM role:
1. From the left panel, select **Roles**.
......@@ -208,7 +208,7 @@ NOTE: **Note:**
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.
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
......
# 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],
message: 'should be on_success, on_failure ' \
'or always' }
validates :expire_in, duration: true
validates :expire_in, duration: { parser: ::Gitlab::Ci::Build::Artifacts::ExpireInParser }
end
end
......
......@@ -6,17 +6,27 @@ module Gitlab
module LegacyValidationHelpers
private
def validate_duration(value)
value.is_a?(String) && ChronicDuration.parse(value)
def validate_duration(value, parser = nil)
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
false
end
def validate_duration_limit(value, limit)
def validate_duration_limit(value, limit, parser = nil)
return false unless value.is_a?(String)
ChronicDuration.parse(value).second.from_now <
ChronicDuration.parse(limit).second.from_now
if parser && parser.respond_to?(:validate_duration_limit)
parser.validate_duration_limit(value, limit)
else
ChronicDuration.parse(value).second.from_now <
ChronicDuration.parse(limit).second.from_now
end
rescue ChronicDuration::DurationParseError
false
end
......
......@@ -106,12 +106,12 @@ module Gitlab
include LegacyValidationHelpers
def validate_each(record, attribute, value)
unless validate_duration(value)
unless validate_duration(value, options[:parser])
record.errors.add(attribute, 'should be a duration')
end
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')
end
end
......
......@@ -56,6 +56,9 @@ module Gitlab
},
terms_opt_in: {
tracking_category: 'Growth::Acquisition::Experiment::TermsOptIn'
},
contact_sales_btn_in_app: {
tracking_category: 'Growth::Conversion::Experiment::ContactSalesInApp'
}
}.freeze
......
......@@ -3824,6 +3824,9 @@ msgstr ""
msgid "BillingPlans|per user"
msgstr ""
msgid "BillingPlan|Contact sales"
msgstr ""
msgid "BillingPlan|Upgrade"
msgstr ""
......@@ -21303,6 +21306,9 @@ msgstr ""
msgid "SecurityConfiguration|Could not retrieve configuration data. Please refresh the page, or try again later."
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}."
msgstr ""
......
......@@ -100,14 +100,11 @@ RSpec.describe 'Profile > Personal Access Tokens', :js do
context "when revocation fails" do
it "displays an error message" do
visit profile_personal_access_tokens_path
allow_any_instance_of(PersonalAccessToken).to receive(:update!).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)
allow_any_instance_of(PersonalAccessTokens::RevokeService).to receive(:revocation_permitted?).and_return(false)
accept_confirm { click_on "Revoke" }
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
......
......@@ -168,6 +168,19 @@ RSpec.describe ApplicationHelper do
it { expect(helper.active_when(false)).to eq(nil) }
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
context 'when alternate support url is specified' do
let(:alternate_url) { 'http://company.example.com/getting-help' }
......
# 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
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|
it "returns artifacts for when #{when_state} defined" do
config = YAML.dump({
......
......@@ -14,7 +14,7 @@ RSpec.describe PersonalAccessTokenPolicy do
end
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(:token_owner) { owned_by_same_user ? user : build(:user) }
let(:token) { build(:personal_access_token, user: token_owner) }
......@@ -26,6 +26,17 @@ RSpec.describe PersonalAccessTokenPolicy do
end
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
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
......@@ -479,6 +479,16 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(job.reload.artifacts_expire_at).to be_nil
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
......
......@@ -73,7 +73,7 @@ RSpec.describe Ci::CreateJobArtifactsService do
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
end
context 'when expire_in params is set' do
context 'when expire_in params is set to a specific value' do
before do
params.merge!('expire_in' => '2 hours')
end
......@@ -89,6 +89,23 @@ RSpec.describe Ci::CreateJobArtifactsService do
expect(metadata_artifact.expire_at).to be_within(1.minute).of(expected_expire_at)
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
......
# 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册