Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									5d3bcd82b5
								
							
						
					
					
						commit
						163b6c3c80
					
				|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -361,6 +361,25 @@ module Types | ||||||
|           complexity: 5, |           complexity: 5, | ||||||
|           resolver: ::Resolvers::TimelogResolver |           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:) |     def label(title:) | ||||||
|       BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| |       BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| | ||||||
|         LabelsFinder |         LabelsFinder | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ class ApplicationSetting < ApplicationRecord | ||||||
|   include Sanitizable |   include Sanitizable | ||||||
| 
 | 
 | ||||||
|   ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22' |   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 |   INSTANCE_REVIEW_MIN_USERS = 50 | ||||||
|   GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \ |   GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \ | ||||||
|  |  | ||||||
|  | @ -22,7 +22,12 @@ class InstanceConfiguration | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def ssh_algorithms_hashes |   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 |   end | ||||||
| 
 | 
 | ||||||
|   def host |   def host | ||||||
|  |  | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Clusters | ||||||
|  |   class AgentPolicy < BasePolicy | ||||||
|  |     alias_method :cluster_agent, :subject | ||||||
|  | 
 | ||||||
|  |     delegate { cluster_agent.project } | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,9 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Clusters | ||||||
|  |   class AgentTokenPolicy < BasePolicy | ||||||
|  |     alias_method :token, :subject | ||||||
|  | 
 | ||||||
|  |     delegate { token.agent } | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -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 | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 262127539fc16715a56e2cf7426f0f8d24922e26847a01a0a15552d71cd148f8 | ||||||
|  | @ -10347,6 +10347,7 @@ CREATE TABLE application_settings ( | ||||||
|     sidekiq_job_limiter_mode smallint DEFAULT 1 NOT NULL, |     sidekiq_job_limiter_mode smallint DEFAULT 1 NOT NULL, | ||||||
|     sidekiq_job_limiter_compression_threshold_bytes integer DEFAULT 100000 NOT NULL, |     sidekiq_job_limiter_compression_threshold_bytes integer DEFAULT 100000 NOT NULL, | ||||||
|     sidekiq_job_limiter_limit_bytes integer DEFAULT 0 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_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_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)), |     CONSTRAINT app_settings_registry_exp_policies_worker_capacity_positive CHECK ((container_registry_expiration_policies_worker_capacity >= 0)), | ||||||
|  |  | ||||||
|  | @ -476,11 +476,10 @@ args: { | ||||||
| #### Set a username | #### Set a username | ||||||
| 
 | 
 | ||||||
| By default, the email in the SAML response is used to automatically generate the | 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, | user's GitLab username.  | ||||||
| assign it to the `nickname` OmniAuth `info` hash attribute. |  | ||||||
| 
 | 
 | ||||||
| For example, if you want to set the `username` attribute in your SAML Response to the username | If you'd like to set another attribute as the username, assign it to the `nickname` OmniAuth `info` | ||||||
| in GitLab, use the following setting: | hash attribute, and add the following setting to your configuration file: | ||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
| args: { | args: { | ||||||
|  | @ -493,6 +492,8 @@ args: { | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | This also sets the `username` attribute in your SAML Response to the username in GitLab. | ||||||
|  | 
 | ||||||
| ### `allowed_clock_drift` | ### `allowed_clock_drift` | ||||||
| 
 | 
 | ||||||
| The clock of the Identity Provider may drift slightly ahead of your system clocks. | The clock of the Identity Provider may drift slightly ahead of your system clocks. | ||||||
|  |  | ||||||
|  | @ -100,7 +100,9 @@ module QA | ||||||
|           attr_writer(name) |           attr_writer(name) | ||||||
| 
 | 
 | ||||||
|           define_method(name) do |           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 | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  | @ -121,9 +123,7 @@ module QA | ||||||
|         return self unless api_resource |         return self unless api_resource | ||||||
| 
 | 
 | ||||||
|         all_attributes.each do |attribute_name| |         all_attributes.each do |attribute_name| | ||||||
|           api_value = api_resource[attribute_name] |           instance_variable_set("@#{attribute_name}", api_resource[attribute_name]) if api_resource.key?(attribute_name) | ||||||
| 
 |  | ||||||
|           instance_variable_set("@#{attribute_name}", api_value) if api_value |  | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         self |         self | ||||||
|  | @ -160,20 +160,17 @@ module QA | ||||||
| 
 | 
 | ||||||
|       private |       private | ||||||
| 
 | 
 | ||||||
|       def populate_attribute(name, block) |       def attribute_value(name, block) | ||||||
|         value = attribute_value(name, block) |         no_api_value = !api_resource&.key?(name) | ||||||
|  |         raise NoValueError, "No value was computed for #{name} of #{self.class.name}." if no_api_value && !block | ||||||
| 
 | 
 | ||||||
|         raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value |         unless no_api_value | ||||||
| 
 |           api_value = api_resource[name] | ||||||
|         value |           log_having_both_api_result_and_block(name, api_value) if block | ||||||
|  |           return api_value | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|       def attribute_value(name, block) |         instance_exec(&block) | ||||||
|         api_value = api_resource&.dig(name) |  | ||||||
| 
 |  | ||||||
|         log_having_both_api_result_and_block(name, api_value) if api_value && block |  | ||||||
| 
 |  | ||||||
|         api_value || (block && instance_exec(&block)) |  | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       # Get all defined attributes across all parents |       # Get all defined attributes across all parents | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -33,6 +33,7 @@ RSpec.describe GitlabSchema.types['Project'] do | ||||||
|       issue_status_counts terraform_states alert_management_integrations |       issue_status_counts terraform_states alert_management_integrations | ||||||
|       container_repositories container_repositories_count |       container_repositories container_repositories_count | ||||||
|       pipeline_analytics squash_read_only sast_ci_configuration |       pipeline_analytics squash_read_only sast_ci_configuration | ||||||
|  |       cluster_agent cluster_agents agent_configurations | ||||||
|       ci_template timelogs |       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_type(Types::Ci::JobTokenScopeType) } | ||||||
|     it { is_expected.to have_graphql_resolver(Resolvers::Ci::JobTokenScopeResolver) } |     it { is_expected.to have_graphql_resolver(Resolvers::Ci::JobTokenScopeResolver) } | ||||||
|   end |   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 | end | ||||||
|  |  | ||||||
|  | @ -31,6 +31,23 @@ RSpec.describe InstanceConfiguration do | ||||||
|           expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size) |           expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size) | ||||||
|         end |         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) |         def pub_file(exist: true) | ||||||
|           path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random' |           path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random' | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
		Loading…
	
		Reference in New Issue