Commit 28c3d8d4 authored by Tiger's avatar Tiger

Add tokens field to cluster agent GraphQL endpoint

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40779
parent 14639321
......@@ -1709,6 +1709,31 @@ type ClusterAgent {
"""
project: Project
"""
Tokens associated with the cluster agent
"""
tokens(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): ClusterAgentTokenConnection
"""
Timestamp the cluster agent was updated
"""
......@@ -1797,6 +1822,26 @@ type ClusterAgentToken {
id: ClustersAgentTokenID!
}
"""
The connection type for ClusterAgentToken.
"""
type ClusterAgentTokenConnection {
"""
A list of edges.
"""
edges: [ClusterAgentTokenEdge]
"""
A list of nodes.
"""
nodes: [ClusterAgentToken]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
Autogenerated input type of ClusterAgentTokenCreate
"""
......@@ -1867,6 +1912,21 @@ type ClusterAgentTokenDeletePayload {
errors: [String!]!
}
"""
An edge in a connection.
"""
type ClusterAgentTokenEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: ClusterAgentToken
}
"""
Identifier of Clusters::Agent
"""
......
......@@ -4659,6 +4659,59 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "tokens",
"description": "Tokens associated with the cluster agent",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "ClusterAgentTokenConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp the cluster agent was updated",
......@@ -4940,6 +4993,73 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ClusterAgentTokenConnection",
"description": "The connection type for ClusterAgentToken.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ClusterAgentTokenEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "ClusterAgentToken",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "ClusterAgentTokenCreateInput",
......@@ -5144,6 +5264,51 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ClusterAgentTokenEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "ClusterAgentToken",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "ClustersAgentID",
# 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.agent_tokens
end
private
def can_read_agent_tokens?
project.feature_available?(:cluster_agents) && current_user.can?(:admin_cluster, project)
end
end
end
end
......@@ -27,6 +27,11 @@ module Types
authorize: :read_project,
resolve: -> (agent, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, agent.project_id).find }
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,
......
---
title: Allow fetching agent tokens from cluster agent GraphQL endpoint
merge_request: 40779
author:
type: added
# 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(:feature_available) { true }
let(:ctx) { Hash(current_user: user) }
let!(:matching_token1) { create(:cluster_agent_token, agent: agent) }
let!(:mathcing_token2) { create(:cluster_agent_token, agent: agent) }
let!(:other_token) { create(:cluster_agent_token) }
subject { resolve(described_class, obj: agent, ctx: ctx) }
before do
stub_licensed_features(cluster_agents: feature_available)
end
it 'returns tokens associated with the agent' do
expect(subject).to contain_exactly(matching_token1, mathcing_token2)
end
context 'feature is not available' do
let(:feature_available) { false }
it { is_expected.to be_empty }
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
......@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ClusterAgent'] do
let(:fields) { %i[created_at id name project updated_at] }
let(:fields) { %i[created_at id name project updated_at tokens] }
it { expect(described_class.graphql_name).to eq('ClusterAgent') }
......
......@@ -231,18 +231,18 @@ RSpec.describe GitlabSchema.types['Project'] do
describe 'cluster_agent' do
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
name
createdAt
updatedAt
project {
id
tokens {
nodes {
id
}
}
}
}
......@@ -260,12 +260,12 @@ RSpec.describe GitlabSchema.types['Project'] do
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(agent['name']).to eq('agent-name')
expect(agent['createdAt']).to be_present
expect(agent['updatedAt']).to be_present
expect(agent['project']['id']).to eq(project.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
end
end
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