Commit ea90f47c authored by James Fargher's avatar James Fargher

Merge branch 'move-agent-functions-to-core' into 'master'

Move Agent CI tunnel functionality to Core

See merge request gitlab-org/gitlab!71972
parents cea9f01e eec9855d
......@@ -7,8 +7,6 @@ module Clusters
end
def execute
return [] unless feature_available?
# closest, most-specific authorization for a given agent wins
(project_authorizations + implicit_authorizations + group_authorizations)
.uniq(&:agent_id)
......@@ -18,10 +16,6 @@ module Clusters
attr_reader :project
def feature_available?
project.licensed_feature_available?(:cluster_agents)
end
def implicit_authorizations
project.cluster_agents.map do |agent|
Clusters::Agents::ImplicitAuthorization.new(agent: agent)
......
......@@ -7,17 +7,11 @@ module Clusters
end
def execute
return ::Clusters::Agent.none unless allowed?
project.cluster_agents.ordered_by_name
end
private
attr_reader :project
def allowed?
project.licensed_feature_available?(:cluster_agents)
end
end
end
# frozen_string_literal: true
module EE
module API
module Ci
module Jobs
extend ActiveSupport::Concern
prepended do
resource :job do
desc 'Get current agents' do
detail 'Retrieves a list of agents for the given job token'
end
route_setting :authentication, job_token_allowed: true
get '/allowed_agents', feature_category: :kubernetes_management do
validate_current_authenticated_job
status 200
pipeline = current_authenticated_job.pipeline
project = current_authenticated_job.project
allowed_agents =
if ::Feature.enabled?(:group_authorized_agents, project, default_enabled: :yaml)
agent_authorizations = ::Clusters::AgentAuthorizationsFinder.new(project).execute
::API::Entities::Clusters::AgentAuthorization.represent(agent_authorizations)
else
associated_agents = ::Clusters::DeployableAgentsFinder.new(project).execute
::API::Entities::Clusters::Agent.represent(associated_agents)
end
{
allowed_agents: allowed_agents,
job: ::API::Entities::Ci::JobRequest::JobInfo.represent(current_authenticated_job),
pipeline: ::API::Entities::Ci::PipelineBasic.represent(pipeline),
project: ::API::Entities::ProjectIdentity.represent(project),
user: ::API::Entities::UserBasic.represent(current_user)
}
end
end
end
end
end
end
end
......@@ -25,139 +25,6 @@ RSpec.describe API::Ci::Jobs do
project.add_developer(developer)
end
describe 'GET /job/allowed_agents' do
let_it_be(:group_authorization) { create(:agent_group_authorization) }
let_it_be(:associated_agent) { create(:cluster_agent, project: project) }
let(:implicit_authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: associated_agent) }
let(:authorizations_finder) { double(execute: [implicit_authorization, group_authorization]) }
let(:api_user) { developer }
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) }
let(:job_status) { 'running' }
let(:params) { {} }
let(:group_authorized_agents_enabled) { true }
subject do
get api('/job/allowed_agents'), headers: headers, params: params
end
before do
stub_licensed_features(cluster_agents: true)
stub_feature_flags(group_authorized_agents: group_authorized_agents_enabled)
allow(Clusters::AgentAuthorizationsFinder).to receive(:new).with(project).and_return(authorizations_finder)
subject
end
context 'when token is valid and user is authorized' do
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => implicit_authorization.agent_id,
'config_project' => hash_including('id' => implicit_authorization.agent.project_id),
'configuration' => implicit_authorization.config
},
{
'id' => group_authorization.agent_id,
'config_project' => hash_including('id' => group_authorization.agent.project_id),
'configuration' => group_authorization.config
}
])
end
context 'when passing the token as params' do
let(:headers) { {} }
let(:params) { { job_token: job.token } }
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => implicit_authorization.agent_id,
'config_project' => hash_including('id' => implicit_authorization.agent.project_id),
'configuration' => implicit_authorization.config
},
{
'id' => group_authorization.agent_id,
'config_project' => a_hash_including('id' => group_authorization.agent.project_id),
'configuration' => group_authorization.config
}
])
end
end
context 'group_authorized_agents feature flag is disabled' do
let(:group_authorized_agents_enabled) { false }
let(:agents_finder) { double(execute: [associated_agent]) }
before do
allow(Clusters::DeployableAgentsFinder).to receive(:new).with(project).and_return(agents_finder)
end
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => associated_agent.id,
'config_project' => hash_including('id' => associated_agent.project_id)
}
])
end
end
end
context 'when user is anonymous' do
let(:api_user) { nil }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is invalid because job has finished' do
let(:job_status) { 'success' }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is invalid' do
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => 'bad_token' } }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is valid but not CI_JOB_TOKEN' do
let(:token) { create(:personal_access_token, user: developer) }
let(:headers) { { 'Private-Token' => token.token } }
it 'returns not found' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET /projects/:id/jobs/:job_id/artifacts' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: :running) }
......
......@@ -177,6 +177,36 @@ module API
present current_authenticated_job, with: Entities::Ci::Job
end
desc 'Get current agents' do
detail 'Retrieves a list of agents for the given job token'
end
route_setting :authentication, job_token_allowed: true
get '/allowed_agents', feature_category: :kubernetes_management do
validate_current_authenticated_job
status 200
pipeline = current_authenticated_job.pipeline
project = current_authenticated_job.project
allowed_agents =
if Feature.enabled?(:group_authorized_agents, project, default_enabled: :yaml)
agent_authorizations = Clusters::AgentAuthorizationsFinder.new(project).execute
Entities::Clusters::AgentAuthorization.represent(agent_authorizations)
else
associated_agents = Clusters::DeployableAgentsFinder.new(project).execute
Entities::Clusters::Agent.represent(associated_agents)
end
{
allowed_agents: allowed_agents,
job: Entities::Ci::JobRequest::JobInfo.represent(current_authenticated_job),
pipeline: Entities::Ci::PipelineBasic.represent(pipeline),
project: Entities::ProjectIdentity.represent(project),
user: Entities::UserBasic.represent(current_user)
}
end
end
helpers do
......@@ -202,5 +232,3 @@ module API
end
end
end
API::Ci::Jobs.prepend_mod_with('API::Ci::Jobs')
......@@ -15,20 +15,8 @@ RSpec.describe Clusters::AgentAuthorizationsFinder do
let_it_be(:staging_agent) { create(:cluster_agent, project: agent_configuration_project) }
let_it_be(:production_agent) { create(:cluster_agent, project: agent_configuration_project) }
let(:feature_available) { true }
subject { described_class.new(requesting_project).execute }
before do
stub_licensed_features(cluster_agents: feature_available)
end
context 'feature is not available' do
let(:feature_available) { false }
it { is_expected.to be_empty }
end
describe 'project authorizations' do
context 'agent configuration project does not share a root namespace with the given project' do
let(:unrelated_agent) { create(:cluster_agent) }
......
......@@ -10,20 +10,6 @@ RSpec.describe Clusters::DeployableAgentsFinder do
subject { described_class.new(project).execute }
before do
stub_licensed_features(cluster_agents: feature_available)
end
context 'feature is available' do
let(:feature_available) { true }
it { is_expected.to contain_exactly(agent) }
end
context 'feature is not available' do
let(:feature_available) { false }
it { is_expected.to be_empty }
end
end
end
......@@ -176,6 +176,137 @@ RSpec.describe API::Ci::Jobs do
end
end
describe 'GET /job/allowed_agents' do
let_it_be(:group_authorization) { create(:agent_group_authorization) }
let_it_be(:associated_agent) { create(:cluster_agent, project: project) }
let(:implicit_authorization) { Clusters::Agents::ImplicitAuthorization.new(agent: associated_agent) }
let(:authorizations_finder) { double(execute: [implicit_authorization, group_authorization]) }
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => job.token } }
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: api_user, status: job_status) }
let(:job_status) { 'running' }
let(:params) { {} }
let(:group_authorized_agents_enabled) { true }
subject do
get api('/job/allowed_agents'), headers: headers, params: params
end
before do
stub_feature_flags(group_authorized_agents: group_authorized_agents_enabled)
allow(Clusters::AgentAuthorizationsFinder).to receive(:new).with(project).and_return(authorizations_finder)
subject
end
context 'when token is valid and user is authorized' do
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => implicit_authorization.agent_id,
'config_project' => hash_including('id' => implicit_authorization.agent.project_id),
'configuration' => implicit_authorization.config
},
{
'id' => group_authorization.agent_id,
'config_project' => hash_including('id' => group_authorization.agent.project_id),
'configuration' => group_authorization.config
}
])
end
context 'when passing the token as params' do
let(:headers) { {} }
let(:params) { { job_token: job.token } }
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => implicit_authorization.agent_id,
'config_project' => hash_including('id' => implicit_authorization.agent.project_id),
'configuration' => implicit_authorization.config
},
{
'id' => group_authorization.agent_id,
'config_project' => a_hash_including('id' => group_authorization.agent.project_id),
'configuration' => group_authorization.config
}
])
end
end
context 'group_authorized_agents feature flag is disabled' do
let(:group_authorized_agents_enabled) { false }
let(:agents_finder) { double(execute: [associated_agent]) }
before do
allow(Clusters::DeployableAgentsFinder).to receive(:new).with(project).and_return(agents_finder)
end
it 'returns agent info', :aggregate_failures do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('job', 'id')).to eq(job.id)
expect(json_response.dig('pipeline', 'id')).to eq(job.pipeline_id)
expect(json_response.dig('project', 'id')).to eq(job.project_id)
expect(json_response.dig('user', 'username')).to eq(api_user.username)
expect(json_response['allowed_agents']).to match_array([
{
'id' => associated_agent.id,
'config_project' => hash_including('id' => associated_agent.project_id)
}
])
end
end
end
context 'when user is anonymous' do
let(:api_user) { nil }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is invalid because job has finished' do
let(:job_status) { 'success' }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is invalid' do
let(:headers) { { API::Ci::Helpers::Runner::JOB_TOKEN_HEADER => 'bad_token' } }
it 'returns unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when token is valid but not CI_JOB_TOKEN' do
let(:token) { create(:personal_access_token, user: user) }
let(:headers) { { 'Private-Token' => token.token } }
it 'returns not found' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET /projects/:id/jobs' do
let(:query) { {} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment