Skip to content
Snippets Groups Projects
Commit e82dab5f authored by Emily Ring's avatar Emily Ring Committed by Steve Abrams
Browse files

Add description field to cluster token

Added description field to Clusters::AgentToken
Updated GraphQL mutation and create service to suport new field
Updated associated tests and docs
parent bb9e095a
No related branches found
No related tags found
No related merge requests found
Showing
with 115 additions and 33 deletions
Loading
Loading
@@ -7,9 +7,11 @@ class AgentToken < ApplicationRecord
 
self.table_name = 'cluster_agent_tokens'
 
belongs_to :agent, class_name: 'Clusters::Agent'
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :created_by_user, class_name: 'User', optional: true
 
before_save :ensure_token
validates :description, length: { maximum: 1024 }
end
end
---
title: Add description field to cluster agent token
merge_request: 54091
author:
type: changed
# frozen_string_literal: true
class AddDescriptionToClusterToken < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless column_exists?(:cluster_agent_tokens, :description)
add_column :cluster_agent_tokens, :description, :text
end
add_text_limit :cluster_agent_tokens, :description, 1024
end
def down
remove_column :cluster_agent_tokens, :description
end
end
3587ba61d003385ea63ce900c1dd1c2bd1f2386abd921615b50421f1b798f553
\ No newline at end of file
Loading
Loading
@@ -11026,6 +11026,8 @@ CREATE TABLE cluster_agent_tokens (
agent_id bigint NOT NULL,
token_encrypted text NOT NULL,
created_by_user_id bigint,
description text,
CONSTRAINT check_4e4ec5070a CHECK ((char_length(description) <= 1024)),
CONSTRAINT check_c60daed227 CHECK ((char_length(token_encrypted) <= 255))
);
 
Loading
Loading
@@ -717,6 +717,7 @@ Autogenerated return type of ClusterAgentDelete.
| `clusterAgent` | ClusterAgent | Cluster agent this token is associated with. |
| `createdAt` | Time | Timestamp the token was created. |
| `createdByUser` | User | The user who created the token. |
| `description` | String | Description of the token. |
| `id` | ClustersAgentTokenID! | Global ID of the token. |
 
### ClusterAgentTokenCreatePayload
Loading
Loading
Loading
Loading
@@ -10,10 +10,16 @@ class Create < BaseMutation
 
ClusterAgentID = ::Types::GlobalIDType[::Clusters::Agent]
 
argument :cluster_agent_id, ClusterAgentID,
argument :cluster_agent_id,
ClusterAgentID,
required: true,
description: 'Global ID of the cluster agent that will be associated with the new token.'
 
argument :description,
GraphQL::STRING_TYPE,
required: false,
description: 'Description of the token.'
field :secret,
GraphQL::STRING_TYPE,
null: true,
Loading
Loading
@@ -24,12 +30,16 @@ class Create < BaseMutation
null: true,
description: 'Token created after mutation.'
 
def resolve(cluster_agent_id:)
cluster_agent = authorized_find!(id: cluster_agent_id)
def resolve(args)
cluster_agent = authorized_find!(id: args[:cluster_agent_id])
 
result = ::Clusters::AgentTokens::CreateService
.new(container: cluster_agent.project, current_user: current_user)
.execute(cluster_agent)
.new(
container: cluster_agent.project,
current_user: current_user,
params: args.merge(agent_id: cluster_agent.id)
)
.execute
 
payload = result.payload
 
Loading
Loading
Loading
Loading
@@ -24,6 +24,11 @@ class AgentTokenType < BaseObject
null: true,
description: 'The user who created the token.'
 
field :description,
GraphQL::STRING_TYPE,
null: true,
description: 'Description of the token.'
field :id,
::Types::GlobalIDType[::Clusters::AgentToken],
null: false,
Loading
Loading
Loading
Loading
@@ -3,11 +3,13 @@
module Clusters
module AgentTokens
class CreateService < ::BaseContainerService
def execute(cluster_agent)
ALLOWED_PARAMS = %i[agent_id description].freeze
def execute
return error_feature_not_available unless container.feature_available?(:cluster_agents)
return error_no_permissions unless current_user.can?(:create_cluster, container)
 
token = ::Clusters::AgentToken.new(agent: cluster_agent, created_by_user: current_user)
token = ::Clusters::AgentToken.new(filtered_params.merge(created_by_user: current_user))
 
if token.save
ServiceResponse.success(payload: { secret: token.token, token: token })
Loading
Loading
@@ -25,6 +27,10 @@ def error_feature_not_available
def error_no_permissions
ServiceResponse.error(message: s_('ClusterAgent|User has insufficient permissions to create a token for this project'))
end
def filtered_params
params.slice(*ALLOWED_PARAMS)
end
end
end
end
Loading
Loading
@@ -18,7 +18,9 @@
specify { expect(described_class).to require_graphql_authorizations(:create_cluster) }
 
describe '#resolve' do
subject { mutation.resolve(cluster_agent_id: cluster_agent.to_global_id) }
let(:description) { 'new token!' }
subject { mutation.resolve(cluster_agent_id: cluster_agent.to_global_id, description: description) }
 
context 'without token permissions' do
it 'raises an error if the resource is not accessible to the user' do
Loading
Loading
@@ -44,10 +46,14 @@
 
it 'creates a new token', :aggregate_failures do
expect { subject }.to change { ::Clusters::AgentToken.count }.by(1)
expect(subject[:secret]).not_to be_nil
expect(subject[:errors]).to eq([])
end
 
it 'returns token information', :aggregate_failures do
expect(subject[:secret]).not_to be_nil
expect(subject[:token].description).to eq(description)
end
context 'invalid params' do
subject { mutation.resolve(cluster_agent_id: cluster_agent.id) }
 
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@
require 'spec_helper'
 
RSpec.describe GitlabSchema.types['ClusterAgentToken'] do
let(:fields) { %i[cluster_agent created_at created_by_user id] }
let(:fields) { %i[cluster_agent created_at created_by_user description id] }
 
it { expect(described_class.graphql_name).to eq('ClusterAgentToken') }
 
Loading
Loading
Loading
Loading
@@ -8,10 +8,11 @@
let_it_be(:cluster_agent) { create(:cluster_agent) }
let_it_be(:current_user) { create(:user) }
 
let(:description) { 'create token' }
let(:mutation) do
graphql_mutation(
:cluster_agent_token_create,
{ cluster_agent_id: cluster_agent.to_global_id.to_s }
{ cluster_agent_id: cluster_agent.to_global_id.to_s, description: description }
)
end
 
Loading
Loading
@@ -49,8 +50,14 @@ def mutation_response
 
it 'creates a new token', :aggregate_failures do
expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Clusters::AgentToken.count }.by(1)
expect(mutation_response['secret']).not_to be_nil
expect(mutation_response['errors']).to eq([])
end
it 'returns token information', :aggregate_failures do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['secret']).not_to be_nil
expect(mutation_response.dig('token', 'description')).to eq(description)
end
end
end
Loading
Loading
@@ -3,43 +3,45 @@
require 'spec_helper'
 
RSpec.describe Clusters::AgentTokens::CreateService do
subject(:service) { described_class.new(container: project, current_user: user) }
subject(:service) { described_class.new(container: project, current_user: user, params: params) }
 
let_it_be(:user) { create(:user) }
let(:cluster_agent) { create(:cluster_agent) }
let(:project) { cluster_agent.project }
let(:params) { { agent_id: cluster_agent.id } }
 
before do
stub_licensed_features(cluster_agents: false)
end
 
describe '#execute' do
subject { service.execute }
context 'without premium plan' do
it 'does not create a new token' do
expect { service.execute(cluster_agent) }.not_to change(Clusters::AgentToken, :count)
expect { subject }.not_to change(Clusters::AgentToken, :count)
end
 
it 'returns missing license error' do
result = service.execute(cluster_agent)
expect(result.status).to eq(:error)
expect(result.message).to eq('This feature is only available for premium plans')
expect(subject.status).to eq(:error)
expect(subject.message).to eq('This feature is only available for premium plans')
end
 
context 'with premium plan' do
let(:description) { 'New token description' }
let(:params) { { agent_id: cluster_agent.id, description: description } }
before do
stub_licensed_features(cluster_agents: true)
end
 
it 'does not create a new token due to user permissions' do
expect { service.execute(cluster_agent) }.not_to change(::Clusters::AgentToken, :count)
expect { subject }.not_to change(::Clusters::AgentToken, :count)
end
 
it 'returns permission errors', :aggregate_failures do
result = service.execute(cluster_agent)
expect(result.status).to eq(:error)
expect(result.message).to eq('User has insufficient permissions to create a token for this project')
expect(subject.status).to eq(:error)
expect(subject.message).to eq('User has insufficient permissions to create a token for this project')
end
 
context 'with user permissions' do
Loading
Loading
@@ -48,21 +50,34 @@
end
 
it 'creates a new token' do
expect { service.execute(cluster_agent) }.to change { ::Clusters::AgentToken.count }.by(1)
expect { subject }.to change { ::Clusters::AgentToken.count }.by(1)
end
 
it 'returns success status', :aggregate_failures do
result = service.execute(cluster_agent)
expect(result.status).to eq(:success)
expect(result.message).to be_nil
expect(subject.status).to eq(:success)
expect(subject.message).to be_nil
end
 
it 'returns token information', :aggregate_failures do
result = service.execute(cluster_agent)
token = subject.payload[:token]
expect(subject.payload[:secret]).not_to be_nil
expect(token.created_by_user).to eq(user)
expect(token.description).to eq(description)
end
context 'when params are invalid' do
let(:params) { { agent_id: 'bad_id' } }
it 'does not create a new token' do
expect { subject }.not_to change(::Clusters::AgentToken, :count)
end
 
expect(result.payload[:secret]).not_to be_nil
expect(result.payload[:token].created_by_user).to eq(user)
it 'returns validation errors', :aggregate_failures do
expect(subject.status).to eq(:error)
expect(subject.message).to eq(['Agent must exist'])
end
end
end
end
Loading
Loading
Loading
Loading
@@ -3,8 +3,9 @@
require 'spec_helper'
 
RSpec.describe Clusters::AgentToken do
it { is_expected.to belong_to(:agent).class_name('Clusters::Agent') }
it { is_expected.to belong_to(:agent).class_name('Clusters::Agent').required }
it { is_expected.to belong_to(:created_by_user).class_name('User').optional }
it { is_expected.to validate_length_of(:description).is_at_most(1024) }
 
describe '#token' do
it 'is generated on save' do
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment