Commit 3c0b6a9f authored by Nick Thomas's avatar Nick Thomas

Merge branch 'emilyring-agent-graphql-delete' into 'master'

Cluster Agent delete mutation for GraphQl

See merge request gitlab-org/gitlab!38622
parents ac23243d 64a85024
......@@ -1655,6 +1655,41 @@ type ClusterAgent {
updatedAt: Time
}
"""
Autogenerated input type of ClusterAgentDelete
"""
input ClusterAgentDeleteInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Global id of the cluster agent that will be deleted
"""
id: ClustersAgentID!
}
"""
Autogenerated return type of ClusterAgentDelete
"""
type ClusterAgentDeletePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
}
"""
Identifier of Clusters::Agent
"""
scalar ClustersAgentID
type Commit {
"""
Author of the commit
......@@ -9646,6 +9681,7 @@ type Mutation {
awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
boardListCreate(input: BoardListCreateInput!): BoardListCreatePayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
clusterAgentDelete(input: ClusterAgentDeleteInput!): ClusterAgentDeletePayload
commitCreate(input: CommitCreateInput!): CommitCreatePayload
configureSast(input: ConfigureSastInput!): ConfigureSastPayload
createAlertIssue(input: CreateAlertIssueInput!): CreateAlertIssuePayload
......
......@@ -4518,6 +4518,104 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "ClusterAgentDeleteInput",
"description": "Autogenerated input type of ClusterAgentDelete",
"fields": null,
"inputFields": [
{
"name": "id",
"description": "Global id of the cluster agent that will be deleted",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ClustersAgentID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ClusterAgentDeletePayload",
"description": "Autogenerated return type of ClusterAgentDelete",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "ClustersAgentID",
"description": "Identifier of Clusters::Agent",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Commit",
......@@ -27273,6 +27371,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "clusterAgentDelete",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "ClusterAgentDeleteInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "ClusterAgentDeletePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "commitCreate",
"description": null,
......@@ -274,6 +274,15 @@ Autogenerated return type of BoardListUpdateLimitMetrics
| `project` | Project | The project this cluster agent is associated with |
| `updatedAt` | Time | Timestamp the cluster agent was updated |
## ClusterAgentDeletePayload
Autogenerated return type of ClusterAgentDelete
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
## Commit
| Name | Type | Description |
......
......@@ -7,6 +7,7 @@ module EE
prepended do
mount_mutation ::Mutations::Clusters::Agents::Create
mount_mutation ::Mutations::Clusters::Agents::Delete
mount_mutation ::Mutations::Issues::SetIteration
mount_mutation ::Mutations::Issues::SetWeight
mount_mutation ::Mutations::Issues::SetEpic
......
# frozen_string_literal: true
module Mutations
module Clusters
module Agents
class Delete < BaseMutation
graphql_name 'ClusterAgentDelete'
authorize :admin_cluster
argument :id,
::Types::GlobalIDType[::Clusters::Agent],
required: true,
description: 'Global id of the cluster agent that will be deleted'
def resolve(id:)
cluster_agent = authorized_find!(id: id)
result = ::Clusters::Agents::DeleteService
.new(container: cluster_agent.project, current_user: current_user)
.execute(cluster_agent)
{
errors: Array.wrap(result.message)
}
end
private
def find_object(id:)
GitlabSchema.find_by_gid(id)
end
end
end
end
end
# frozen_string_literal: true
module Clusters
module Agents
class DeleteService < ::BaseContainerService
def execute(cluster_agent)
return error_no_permissions unless current_user.can?(:admin_cluster, cluster_agent)
if cluster_agent.destroy
ServiceResponse.success
else
ServiceResponse.error(message: cluster_agent.errors.full_messages)
end
end
private
def error_no_permissions
ServiceResponse.error(message: s_('ClusterAgent|You have insufficient permissions to delete this cluster agent'))
end
end
end
end
---
title: Added Cluster Agent delete mutation for GraphQl
merge_request: 38622
author:
type: changed
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Clusters::Agents::Delete do
subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
let(:cluster_agent) { create(:cluster_agent) }
let(:project) { cluster_agent.project }
let(:user) { create(:user) }
let(:context) do
GraphQL::Query::Context.new(
query: OpenStruct.new(schema: nil),
values: { current_user: user },
object: nil
)
end
specify { expect(described_class).to require_graphql_authorizations(:admin_cluster) }
describe '#resolve' do
subject { mutation.resolve(id: cluster_agent.to_global_id) }
context 'without user permissions' do
it 'fails to delete the cluster agent', :aggregate_failures do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with user permissions' do
before do
project.add_maintainer(user)
end
it 'deletes a cluster agent', :aggregate_failures do
expect { subject }.to change { ::Clusters::Agent.count }.by(-1)
expect { cluster_agent.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with invalid params' do
subject { mutation.resolve(id: cluster_agent.id) }
it 'raises an error if the cluster agent id is invalid', :aggregate_failures do
expect { subject }.to raise_error(NoMethodError)
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Delete a cluster agent' do
include GraphqlHelpers
let(:cluster_agent) { create(:cluster_agent) }
let(:project) { cluster_agent.project }
let(:current_user) { create(:user) }
let(:mutation) do
graphql_mutation(
:cluster_agent_delete,
{ id: cluster_agent.to_global_id.uri }
)
end
def mutation_response
graphql_mutation_response(:cluster_agent_delete)
end
context 'without project permissions' do
it_behaves_like 'a mutation that returns top-level errors',
errors: ['The resource that you are attempting to access does not exist '\
'or you don\'t have permission to perform this action']
it 'does not delete cluster agent' do
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with premium plan and project permissions' do
before do
allow(License).to receive(:current).and_return(create(:license, plan: ::License::PREMIUM_PLAN))
project.add_maintainer(current_user)
end
it 'deletes a cluster agent', :aggregate_failures do
expect { post_graphql_mutation(mutation, current_user: current_user) }.to change { Clusters::Agent.count }.by(-1)
expect(mutation_response['errors']).to eq([])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Agents::DeleteService do
subject(:service) { described_class.new(container: project, current_user: user) }
let(:cluster_agent) { create(:cluster_agent) }
let(:project) { cluster_agent.project }
let(:user) { create(:user) }
describe '#execute' do
context 'without user permissions' do
it 'fails to delete when the user has no permissions', :aggregate_failures do
response = service.execute(cluster_agent)
expect(response.status).to eq(:error)
expect(response.message).to eq('You have insufficient permissions to delete this cluster agent')
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'with user permissions' do
before do
project.add_maintainer(user)
end
it 'deletes a cluster agent', :aggregate_failures do
expect { service.execute(cluster_agent) }.to change { ::Clusters::Agent.count }.by(-1)
expect { cluster_agent.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
......@@ -5114,6 +5114,9 @@ msgstr ""
msgid "ClusterAgent|You have insufficient permissions to create a cluster agent for this project"
msgstr ""
msgid "ClusterAgent|You have insufficient permissions to delete this cluster agent"
msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
......
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