Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-09-23 03:09:49 +00:00
parent 5d3bcd82b5
commit 163b6c3c80
32 changed files with 908 additions and 21 deletions

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Resolvers
module Clusters
class AgentTokensResolver < BaseResolver
type Types::Clusters::AgentTokenType, null: true
alias_method :agent, :object
delegate :project, to: :agent
def resolve(**args)
return ::Clusters::AgentToken.none unless can_read_agent_tokens?
agent.last_used_agent_tokens
end
private
def can_read_agent_tokens?
current_user.can?(:admin_cluster, project)
end
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Resolvers
module Clusters
class AgentsResolver < BaseResolver
include LooksAhead
type Types::Clusters::AgentType.connection_type, null: true
extras [:lookahead]
when_single do
argument :name, GraphQL::Types::String,
required: true,
description: 'Name of the cluster agent.'
end
alias_method :project, :object
def resolve_with_lookahead(**args)
apply_lookahead(
::Clusters::AgentsFinder
.new(project, current_user, params: args)
.execute
)
end
private
def preloads
{ tokens: :last_used_agent_tokens }
end
end
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Resolvers
module Kas
class AgentConfigurationsResolver < BaseResolver
type Types::Kas::AgentConfigurationType, null: true
# Calls Gitaly via KAS
calls_gitaly!
alias_method :project, :object
def resolve
return [] unless can_read_agent_configuration?
kas_client.list_agent_config_files(project: project)
rescue GRPC::BadStatus => e
raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name
end
private
def can_read_agent_configuration?
current_user.can?(:admin_cluster, project)
end
def kas_client
@kas_client ||= Gitlab::Kas::Client.new
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Resolvers
module Kas
class AgentConnectionsResolver < BaseResolver
type Types::Kas::AgentConnectionType, null: true
alias_method :agent, :object
delegate :project, to: :agent
def resolve
return [] unless can_read_connected_agents?
BatchLoader::GraphQL.for(agent.id).batch(key: project, default_value: []) do |agent_ids, loader|
agents = get_connected_agents.group_by(&:agent_id).slice(*agent_ids)
agents.each do |agent_id, connections|
loader.call(agent_id, connections)
end
end
end
private
def can_read_connected_agents?
current_user.can?(:admin_cluster, project)
end
def get_connected_agents
kas_client.get_connected_agents(project: project)
rescue GRPC::BadStatus => e
raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name
end
def kas_client
@kas_client ||= Gitlab::Kas::Client.new
end
end
end
end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
module Types
module Clusters
class AgentTokenType < BaseObject
graphql_name 'ClusterAgentToken'
authorize :admin_cluster
connection_type_class(Types::CountableConnectionType)
field :cluster_agent,
Types::Clusters::AgentType,
description: 'Cluster agent this token is associated with.',
null: true
field :created_at,
Types::TimeType,
null: true,
description: 'Timestamp the token was created.'
field :created_by_user,
Types::UserType,
null: true,
description: 'User who created the token.'
field :description,
GraphQL::Types::String,
null: true,
description: 'Description of the token.'
field :last_used_at,
Types::TimeType,
null: true,
description: 'Timestamp the token was last used.'
field :id,
::Types::GlobalIDType[::Clusters::AgentToken],
null: false,
description: 'Global ID of the token.'
field :name,
GraphQL::Types::String,
null: true,
description: 'Name given to the token.'
def cluster_agent
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Clusters::Agent, object.agent_id).find
end
end
end
end

View File

@ -0,0 +1,67 @@
# frozen_string_literal: true
module Types
module Clusters
class AgentType < BaseObject
graphql_name 'ClusterAgent'
authorize :admin_cluster
connection_type_class(Types::CountableConnectionType)
field :created_at,
Types::TimeType,
null: true,
description: 'Timestamp the cluster agent was created.'
field :created_by_user,
Types::UserType,
null: true,
description: 'User object, containing information about the person who created the agent.'
field :id, GraphQL::Types::ID,
null: false,
description: 'ID of the cluster agent.'
field :name,
GraphQL::Types::String,
null: true,
description: 'Name of the cluster agent.'
field :project, Types::ProjectType,
description: 'Project this cluster agent is associated with.',
null: true,
authorize: :read_project
field :tokens, Types::Clusters::AgentTokenType.connection_type,
description: 'Tokens associated with the cluster agent.',
null: true,
resolver: ::Resolvers::Clusters::AgentTokensResolver
field :updated_at,
Types::TimeType,
null: true,
description: 'Timestamp the cluster agent was updated.'
field :web_path,
GraphQL::Types::String,
null: true,
description: 'Web path of the cluster agent.'
field :connections,
Types::Kas::AgentConnectionType.connection_type,
null: true,
description: 'Active connections for the cluster agent',
complexity: 5,
resolver: ::Resolvers::Kas::AgentConnectionsResolver
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
def web_path
::Gitlab::Routing.url_helpers.project_cluster_agent_path(object.project, object.name)
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Types
module Kas
# rubocop: disable Graphql/AuthorizeTypes
class AgentConfigurationType < BaseObject
graphql_name 'AgentConfiguration'
description 'Configuration details for an Agent'
field :agent_name,
GraphQL::Types::String,
null: true,
description: 'Name of the agent.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Types
module Kas
# rubocop: disable Graphql/AuthorizeTypes
class AgentConnectionType < BaseObject
graphql_name 'ConnectedAgent'
description 'Connection details for an Agent'
field :connected_at,
Types::TimeType,
null: true,
description: 'When the connection was established.'
field :connection_id,
GraphQL::Types::BigInt,
null: true,
description: 'ID of the connection.'
field :metadata,
Types::Kas::AgentMetadataType,
method: :agent_meta,
null: true,
description: 'Information about the Agent.'
def connected_at
Time.at(object.connected_at.seconds)
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Types
module Kas
# rubocop: disable Graphql/AuthorizeTypes
class AgentMetadataType < BaseObject
graphql_name 'AgentMetadata'
description 'Information about a connected Agent'
field :version,
GraphQL::Types::String,
null: true,
description: 'Agent version tag.'
field :commit,
GraphQL::Types::String,
method: :commit_id,
null: true,
description: 'Agent version commit.'
field :pod_namespace,
GraphQL::Types::String,
null: true,
description: 'Namespace of the pod running the Agent.'
field :pod_name,
GraphQL::Types::String,
null: true,
description: 'Name of the pod running the Agent.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -361,6 +361,25 @@ module Types
complexity: 5,
resolver: ::Resolvers::TimelogResolver
field :agent_configurations,
::Types::Kas::AgentConfigurationType.connection_type,
null: true,
description: 'Agent configurations defined by the project',
resolver: ::Resolvers::Kas::AgentConfigurationsResolver
field :cluster_agent,
::Types::Clusters::AgentType,
null: true,
description: 'Find a single cluster agent by name.',
resolver: ::Resolvers::Clusters::AgentsResolver.single
field :cluster_agents,
::Types::Clusters::AgentType.connection_type,
extras: [:lookahead],
null: true,
description: 'Cluster agents associated with the project.',
resolver: ::Resolvers::Clusters::AgentsResolver
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
LabelsFinder

View File

@ -9,7 +9,6 @@ class ApplicationSetting < ApplicationRecord
include Sanitizable
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
ignore_column :seat_link_enabled, remove_with: '14.4', remove_after: '2021-09-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \

View File

@ -22,7 +22,12 @@ class InstanceConfiguration
private
def ssh_algorithms_hashes
SSH_ALGORITHMS.map { |algo| ssh_algorithm_hashes(algo) }.compact
SSH_ALGORITHMS.select { |algo| ssh_algorithm_enabled?(algo) }.map { |algo| ssh_algorithm_hashes(algo) }.compact
end
def ssh_algorithm_enabled?(algorithm)
algorithm_key_restriction = application_settings["#{algorithm.downcase}_key_restriction"]
algorithm_key_restriction.nil? || algorithm_key_restriction != ApplicationSetting::FORBIDDEN_KEY_VALUE
end
def host

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Clusters
class AgentPolicy < BasePolicy
alias_method :cluster_agent, :subject
delegate { cluster_agent.project }
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Clusters
class AgentTokenPolicy < BasePolicy
alias_method :token, :subject
delegate { token.agent }
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSuggestPipelineEnabledToApplicationSettings < Gitlab::Database::Migration[1.0]
def change
add_column :application_settings, :suggest_pipeline_enabled, :boolean, default: true, null: false
end
end

View File

@ -0,0 +1 @@
262127539fc16715a56e2cf7426f0f8d24922e26847a01a0a15552d71cd148f8

View File

@ -10347,6 +10347,7 @@ CREATE TABLE application_settings (
sidekiq_job_limiter_mode smallint DEFAULT 1 NOT NULL,
sidekiq_job_limiter_compression_threshold_bytes integer DEFAULT 100000 NOT NULL,
sidekiq_job_limiter_limit_bytes integer DEFAULT 0 NOT NULL,
suggest_pipeline_enabled boolean DEFAULT true NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)),

View File

@ -476,11 +476,10 @@ args: {
#### Set a username
By default, the email in the SAML response is used to automatically generate the
user's GitLab username. If you'd like to set another attribute as the username,
assign it to the `nickname` OmniAuth `info` hash attribute.
user's GitLab username.
For example, if you want to set the `username` attribute in your SAML Response to the username
in GitLab, use the following setting:
If you'd like to set another attribute as the username, assign it to the `nickname` OmniAuth `info`
hash attribute, and add the following setting to your configuration file:
```yaml
args: {
@ -493,6 +492,8 @@ args: {
}
```
This also sets the `username` attribute in your SAML Response to the username in GitLab.
### `allowed_clock_drift`
The clock of the Identity Provider may drift slightly ahead of your system clocks.

View File

@ -100,7 +100,9 @@ module QA
attr_writer(name)
define_method(name) do
instance_variable_get("@#{name}") || instance_variable_set("@#{name}", populate_attribute(name, block))
return instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
instance_variable_set("@#{name}", attribute_value(name, block))
end
end
@ -121,9 +123,7 @@ module QA
return self unless api_resource
all_attributes.each do |attribute_name|
api_value = api_resource[attribute_name]
instance_variable_set("@#{attribute_name}", api_value) if api_value
instance_variable_set("@#{attribute_name}", api_resource[attribute_name]) if api_resource.key?(attribute_name)
end
self
@ -160,20 +160,17 @@ module QA
private
def populate_attribute(name, block)
value = attribute_value(name, block)
raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value
value
end
def attribute_value(name, block)
api_value = api_resource&.dig(name)
no_api_value = !api_resource&.key?(name)
raise NoValueError, "No value was computed for #{name} of #{self.class.name}." if no_api_value && !block
log_having_both_api_result_and_block(name, api_value) if api_value && block
unless no_api_value
api_value = api_resource[name]
log_having_both_api_result_and_block(name, api_value) if block
return api_value
end
api_value || (block && instance_exec(&block))
instance_exec(&block)
end
# Get all defined attributes across all parents

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Clusters::AgentTokensResolver do
include GraphqlHelpers
it { expect(described_class.type).to eq(Types::Clusters::AgentTokenType) }
it { expect(described_class.null).to be_truthy }
describe '#resolve' do
let(:agent) { create(:cluster_agent) }
let(:user) { create(:user, maintainer_projects: [agent.project]) }
let(:ctx) { Hash(current_user: user) }
let!(:matching_token1) { create(:cluster_agent_token, agent: agent, last_used_at: 5.days.ago) }
let!(:matching_token2) { create(:cluster_agent_token, agent: agent, last_used_at: 2.days.ago) }
let!(:other_token) { create(:cluster_agent_token) }
subject { resolve(described_class, obj: agent, ctx: ctx) }
it 'returns tokens associated with the agent, ordered by last_used_at' do
expect(subject).to eq([matching_token2, matching_token1])
end
context 'user does not have permission' do
let(:user) { create(:user, developer_projects: [agent.project]) }
it { is_expected.to be_empty }
end
end
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Clusters::AgentsResolver do
include GraphqlHelpers
specify do
expect(described_class).to have_nullable_graphql_type(Types::Clusters::AgentType.connection_type)
end
specify do
expect(described_class.field_options).to include(extras: include(:lookahead))
end
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:maintainer) { create(:user, maintainer_projects: [project]) }
let_it_be(:developer) { create(:user, developer_projects: [project]) }
let_it_be(:agents) { create_list(:cluster_agent, 2, project: project) }
let(:ctx) { { current_user: current_user } }
subject { resolve_agents }
context 'the current user has access to clusters' do
let(:current_user) { maintainer }
it 'finds all agents' do
expect(subject).to match_array(agents)
end
end
context 'the current user does not have access to clusters' do
let(:current_user) { developer }
it 'returns an empty result' do
expect(subject).to be_empty
end
end
end
def resolve_agents(args = {})
resolve(described_class, obj: project, ctx: ctx, lookahead: positive_lookahead, args: args)
end
end
RSpec.describe Resolvers::Clusters::AgentsResolver.single do
it { expect(described_class).to be < Resolvers::Clusters::AgentsResolver }
describe '.field_options' do
subject { described_class.field_options }
specify do
expect(subject).to include(
type: ::Types::Clusters::AgentType,
null: true,
extras: [:lookahead]
)
end
end
describe 'arguments' do
subject { described_class.arguments[argument] }
describe 'name' do
let(:argument) { 'name' }
it do
expect(subject).to be_present
expect(subject.type).to be_kind_of GraphQL::Schema::NonNull
expect(subject.type.unwrap).to eq GraphQL::Types::String
expect(subject.description).to be_present
end
end
end
end

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Kas::AgentConfigurationsResolver do
include GraphqlHelpers
it { expect(described_class.type).to eq(Types::Kas::AgentConfigurationType) }
it { expect(described_class.null).to be_truthy }
it { expect(described_class.field_options).to include(calls_gitaly: true) }
describe '#resolve' do
let_it_be(:project) { create(:project) }
let(:user) { create(:user, maintainer_projects: [project]) }
let(:ctx) { Hash(current_user: user) }
let(:agent1) { double }
let(:agent2) { double }
let(:kas_client) { instance_double(Gitlab::Kas::Client, list_agent_config_files: [agent1, agent2]) }
subject { resolve(described_class, obj: project, ctx: ctx) }
before do
allow(Gitlab::Kas::Client).to receive(:new).and_return(kas_client)
end
it 'returns agents configured for the project' do
expect(subject).to contain_exactly(agent1, agent2)
end
context 'an error is returned from the KAS client' do
before do
allow(kas_client).to receive(:list_agent_config_files).and_raise(GRPC::DeadlineExceeded)
end
it 'raises a graphql error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, 'GRPC::DeadlineExceeded')
end
end
context 'user does not have permission' do
let(:user) { create(:user) }
it { is_expected.to be_empty }
end
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Kas::AgentConnectionsResolver do
include GraphqlHelpers
it { expect(described_class.type).to eq(Types::Kas::AgentConnectionType) }
it { expect(described_class.null).to be_truthy }
describe '#resolve' do
let_it_be(:project) { create(:project) }
let_it_be(:agent1) { create(:cluster_agent, project: project) }
let_it_be(:agent2) { create(:cluster_agent, project: project) }
let(:user) { create(:user, maintainer_projects: [project]) }
let(:ctx) { Hash(current_user: user) }
let(:connection1) { double(agent_id: agent1.id) }
let(:connection2) { double(agent_id: agent1.id) }
let(:connection3) { double(agent_id: agent2.id) }
let(:connected_agents) { [connection1, connection2, connection3] }
let(:kas_client) { instance_double(Gitlab::Kas::Client, get_connected_agents: connected_agents) }
subject do
batch_sync do
resolve(described_class, obj: agent1, ctx: ctx)
end
end
before do
allow(Gitlab::Kas::Client).to receive(:new).and_return(kas_client)
end
it 'returns active connections for the agent' do
expect(subject).to contain_exactly(connection1, connection2)
end
it 'queries KAS once when multiple agents are requested' do
expect(kas_client).to receive(:get_connected_agents).once
response = batch_sync do
resolve(described_class, obj: agent1, ctx: ctx)
resolve(described_class, obj: agent2, ctx: ctx)
end
expect(response).to contain_exactly(connection3)
end
context 'an error is returned from the KAS client' do
before do
allow(kas_client).to receive(:get_connected_agents).and_raise(GRPC::DeadlineExceeded)
end
it 'raises a graphql error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable, 'GRPC::DeadlineExceeded')
end
end
context 'user does not have permission' do
let(:user) { create(:user) }
it { is_expected.to be_empty }
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ClusterAgentToken'] do
let(:fields) { %i[cluster_agent created_at created_by_user description id last_used_at name] }
it { expect(described_class.graphql_name).to eq('ClusterAgentToken') }
it { expect(described_class).to require_graphql_authorizations(:admin_cluster) }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['ClusterAgent'] do
let(:fields) { %i[created_at created_by_user id name project updated_at tokens web_path connections] }
it { expect(described_class.graphql_name).to eq('ClusterAgent') }
it { expect(described_class).to require_graphql_authorizations(:admin_cluster) }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['AgentConfiguration'] do
let(:fields) { %i[agent_name] }
it { expect(described_class.graphql_name).to eq('AgentConfiguration') }
it { expect(described_class.description).to eq('Configuration details for an Agent') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Kas::AgentConnectionType do
include GraphqlHelpers
let(:fields) { %i[connected_at connection_id metadata] }
it { expect(described_class.graphql_name).to eq('ConnectedAgent') }
it { expect(described_class.description).to eq('Connection details for an Agent') }
it { expect(described_class).to have_graphql_fields(fields) }
describe '#connected_at' do
let(:connected_at) { double(Google::Protobuf::Timestamp, seconds: 123456, nanos: 654321) }
let(:object) { double(Gitlab::Agent::AgentTracker::ConnectedAgentInfo, connected_at: connected_at) }
it 'converts the seconds value to a timestamp' do
expect(resolve_field(:connected_at, object)).to eq(Time.at(connected_at.seconds))
end
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Kas::AgentMetadataType do
include GraphqlHelpers
let(:fields) { %i[version commit pod_namespace pod_name] }
it { expect(described_class.graphql_name).to eq('AgentMetadata') }
it { expect(described_class.description).to eq('Information about a connected Agent') }
it { expect(described_class).to have_graphql_fields(fields) }
end

View File

@ -33,6 +33,7 @@ RSpec.describe GitlabSchema.types['Project'] do
issue_status_counts terraform_states alert_management_integrations
container_repositories container_repositories_count
pipeline_analytics squash_read_only sast_ci_configuration
cluster_agent cluster_agents agent_configurations
ci_template timelogs
]
@ -458,4 +459,137 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::Ci::JobTokenScopeType) }
it { is_expected.to have_graphql_resolver(Resolvers::Ci::JobTokenScopeResolver) }
end
describe 'agent_configurations' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
agentConfigurations {
nodes {
agentName
}
}
}
}
)
end
let(:agent_name) { 'example-agent-name' }
let(:kas_client) { instance_double(Gitlab::Kas::Client, list_agent_config_files: [double(agent_name: agent_name)]) }
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_maintainer(user)
allow(Gitlab::Kas::Client).to receive(:new).and_return(kas_client)
end
it 'returns configured agents' do
agents = subject.dig('data', 'project', 'agentConfigurations', 'nodes')
expect(agents.count).to eq(1)
expect(agents.first['agentName']).to eq(agent_name)
end
end
describe 'cluster_agents' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:cluster_agent) { create(:cluster_agent, project: project, name: 'agent-name') }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
clusterAgents {
count
nodes {
id
name
createdAt
updatedAt
project {
id
}
}
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_maintainer(user)
end
it 'returns associated cluster agents' do
agents = subject.dig('data', 'project', 'clusterAgents', 'nodes')
expect(agents.count).to be(1)
expect(agents.first['id']).to eq(cluster_agent.to_global_id.to_s)
expect(agents.first['name']).to eq('agent-name')
expect(agents.first['createdAt']).to be_present
expect(agents.first['updatedAt']).to be_present
expect(agents.first['project']['id']).to eq(project.to_global_id.to_s)
end
it 'returns count of cluster agents' do
count = subject.dig('data', 'project', 'clusterAgents', 'count')
expect(count).to be(project.cluster_agents.size)
end
end
describe 'cluster_agent' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:cluster_agent) { create(:cluster_agent, project: project, name: 'agent-name') }
let_it_be(:agent_token) { create(:cluster_agent_token, agent: cluster_agent) }
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
clusterAgent(name: "#{cluster_agent.name}") {
id
tokens {
count
nodes {
id
}
}
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before do
project.add_maintainer(user)
end
it 'returns associated cluster agents' do
agent = subject.dig('data', 'project', 'clusterAgent')
tokens = agent.dig('tokens', 'nodes')
expect(agent['id']).to eq(cluster_agent.to_global_id.to_s)
expect(tokens.count).to be(1)
expect(tokens.first['id']).to eq(agent_token.to_global_id.to_s)
end
it 'returns count of agent tokens' do
agent = subject.dig('data', 'project', 'clusterAgent')
count = agent.dig('tokens', 'count')
expect(cluster_agent.agent_tokens.size).to be(count)
end
end
end

View File

@ -31,6 +31,23 @@ RSpec.describe InstanceConfiguration do
expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size)
end
it 'includes all algorithms' do
stub_pub_file(pub_file)
result = subject.settings[:ssh_algorithms_hashes]
expect(result.map { |a| a[:name] }).to match_array(%w(DSA ECDSA ED25519 RSA))
end
it 'does not include disabled algorithm' do
Gitlab::CurrentSettings.current_application_settings.update!(dsa_key_restriction: ApplicationSetting::FORBIDDEN_KEY_VALUE)
stub_pub_file(pub_file)
result = subject.settings[:ssh_algorithms_hashes]
expect(result.map { |a| a[:name] }).to match_array(%w(ECDSA ED25519 RSA))
end
def pub_file(exist: true)
path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random'

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::AgentPolicy do
let(:cluster_agent) { create(:cluster_agent, name: 'agent' )}
let(:user) { create(:admin) }
let(:policy) { described_class.new(user, cluster_agent) }
let(:project) { cluster_agent.project }
describe 'rules' do
context 'when developer' do
before do
project.add_developer(user)
end
it { expect(policy).to be_disallowed :admin_cluster }
end
context 'when maintainer' do
before do
project.add_maintainer(user)
end
it { expect(policy).to be_allowed :admin_cluster }
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::AgentTokenPolicy do
let_it_be(:token) { create(:cluster_agent_token) }
let(:user) { create(:user) }
let(:policy) { described_class.new(user, token) }
let(:project) { token.agent.project }
describe 'rules' do
context 'when developer' do
before do
project.add_developer(user)
end
it { expect(policy).to be_disallowed :admin_cluster }
it { expect(policy).to be_disallowed :read_cluster }
end
context 'when maintainer' do
before do
project.add_maintainer(user)
end
it { expect(policy).to be_allowed :admin_cluster }
it { expect(policy).to be_allowed :read_cluster }
end
end
end