Commit 460f2512 authored by rossfuhrman's avatar rossfuhrman Committed by Shinya Maeda

Convert to actual GraphQL types

Convert Mutations::Security::CiConfiguration to use
actual GraphQL types instead of GraphQL::Types::JSON
parent c54c3f20
...@@ -2193,12 +2193,12 @@ input ConfigureSastInput { ...@@ -2193,12 +2193,12 @@ input ConfigureSastInput {
clientMutationId: String clientMutationId: String
""" """
Payload containing SAST variable values (https://docs.gitlab.com/ee/user/application_security/sast/#available-variables). SAST CI configuration for the project
""" """
configuration: JSON! configuration: SastCiConfigurationInput!
""" """
Full path of the project. Full path of the project
""" """
projectPath: ID! projectPath: ID!
} }
...@@ -2218,9 +2218,14 @@ type ConfigureSastPayload { ...@@ -2218,9 +2218,14 @@ type ConfigureSastPayload {
errors: [String!]! errors: [String!]!
""" """
JSON containing the status of MR creation. Status of creating the commit for the supplied SAST CI configuration
"""
status: String!
""" """
result: JSON Redirect path to use when the response is successful
"""
successPath: String
} }
""" """
...@@ -14666,6 +14671,41 @@ type SastCiConfigurationEntityEdge { ...@@ -14666,6 +14671,41 @@ type SastCiConfigurationEntityEdge {
node: SastCiConfigurationEntity node: SastCiConfigurationEntity
} }
"""
Represents an entity in SAST CI configuration
"""
input SastCiConfigurationEntityInput {
"""
Default value that is used if value is empty
"""
defaultValue: String!
"""
CI keyword of entity
"""
field: String!
"""
Current value of the entity
"""
value: String!
}
"""
Represents a CI configuration of SAST
"""
input SastCiConfigurationInput {
"""
List of global entities related to SAST configuration
"""
global: [SastCiConfigurationEntityInput!]
"""
List of pipeline entities related to SAST configuration
"""
pipeline: [SastCiConfigurationEntityInput!]
}
""" """
Represents an entity for options in SAST CI configuration Represents an entity for options in SAST CI configuration
""" """
......
...@@ -5971,7 +5971,7 @@ ...@@ -5971,7 +5971,7 @@
"inputFields": [ "inputFields": [
{ {
"name": "projectPath", "name": "projectPath",
"description": "Full path of the project.", "description": "Full path of the project",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
...@@ -5985,13 +5985,13 @@ ...@@ -5985,13 +5985,13 @@
}, },
{ {
"name": "configuration", "name": "configuration",
"description": "Payload containing SAST variable values (https://docs.gitlab.com/ee/user/application_security/sast/#available-variables).", "description": "SAST CI configuration for the project",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": {
"kind": "SCALAR", "kind": "INPUT_OBJECT",
"name": "JSON", "name": "SastCiConfigurationInput",
"ofType": null "ofType": null
} }
}, },
...@@ -6058,14 +6058,32 @@ ...@@ -6058,14 +6058,32 @@
"deprecationReason": null "deprecationReason": null
}, },
{ {
"name": "result", "name": "status",
"description": "JSON containing the status of MR creation.", "description": "Status of creating the commit for the supplied SAST CI configuration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "successPath",
"description": "Redirect path to use when the response is successful",
"args": [ "args": [
], ],
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "JSON", "name": "String",
"ofType": null "ofType": null
}, },
"isDeprecated": false, "isDeprecated": false,
...@@ -42812,6 +42830,106 @@ ...@@ -42812,6 +42830,106 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "SastCiConfigurationEntityInput",
"description": "Represents an entity in SAST CI configuration",
"fields": null,
"inputFields": [
{
"name": "field",
"description": "CI keyword of entity",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "defaultValue",
"description": "Default value that is used if value is empty",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "value",
"description": "Current value of the entity",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "SastCiConfigurationInput",
"description": "Represents a CI configuration of SAST",
"fields": null,
"inputFields": [
{
"name": "global",
"description": "List of global entities related to SAST configuration",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "SastCiConfigurationEntityInput",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "pipeline",
"description": "List of pipeline entities related to SAST configuration",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "SastCiConfigurationEntityInput",
"ofType": null
}
}
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "SastCiConfigurationOptionsEntity", "name": "SastCiConfigurationOptionsEntity",
...@@ -369,7 +369,8 @@ Autogenerated return type of ConfigureSast ...@@ -369,7 +369,8 @@ Autogenerated return type of ConfigureSast
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. | | `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `result` | JSON | JSON containing the status of MR creation. | | `status` | String! | Status of creating the commit for the supplied SAST CI configuration |
| `successPath` | String | Redirect path to use when the response is successful |
## ContainerExpirationPolicy ## ContainerExpirationPolicy
......
...@@ -10,37 +10,55 @@ module Mutations ...@@ -10,37 +10,55 @@ module Mutations
argument :project_path, GraphQL::ID_TYPE, argument :project_path, GraphQL::ID_TYPE,
required: true, required: true,
description: 'Full path of the project.' description: 'Full path of the project'
argument :configuration, GraphQL::Types::JSON, # rubocop:disable Graphql/JSONType argument :configuration, ::Types::CiConfiguration::Sast::InputType,
required: true, required: true,
description: 'Payload containing SAST variable values (https://docs.gitlab.com/ee/user/application_security/sast/#available-variables).' description: 'SAST CI configuration for the project'
field :result, # rubocop:disable Graphql/JSONType field :status, GraphQL::STRING_TYPE, null: false,
GraphQL::Types::JSON, description: 'Status of creating the commit for the supplied SAST CI configuration'
null: true,
description: 'JSON containing the status of MR creation.' field :success_path, GraphQL::STRING_TYPE, null: true,
description: 'Redirect path to use when the response is successful'
authorize :push_code authorize :push_code
def resolve(project_path:, configuration:) def resolve(project_path:, configuration:)
project = authorized_find!(full_path: project_path) project = authorized_find!(full_path: project_path)
format_json(::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute) validate_flag!(project)
sast_create_service_params = format_for_service(configuration)
result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, sast_create_service_params).execute
prepare_response(result)
end end
private private
def validate_flag!(project)
return if ::Feature.enabled?(:security_sast_configuration, project)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'security_sast_configuration flag is not enabled on this project'
end
def find_object(full_path:) def find_object(full_path:)
resolve_project(full_path: full_path) resolve_project(full_path: full_path)
end end
def format_json(result) # Temporary formatting necessary for supporting REST API
# Will be removed during the implementation of
# https://gitlab.com/gitlab-org/gitlab/-/issues/246737
def format_for_service(configuration)
global_defaults = configuration["global"]&.collect {|k| [k["field"], k["defaultValue"]]}.to_h
pipeline_defaults = configuration["pipeline"]&.collect {|k| [k["field"], k["defaultValue"]]}.to_h
global_defaults.merge!(pipeline_defaults)
end
def prepare_response(result)
{ {
result: { status: result[:status],
status: result[:status], success_path: result[:success_path],
success_path: result[:success_path], errors: Array(result[:errors])
errors: result[:errors]
}
} }
end end
end end
......
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
# rubocop: disable Graphql/AuthorizeTypes
class EntityInputType < BaseInputObject
graphql_name 'SastCiConfigurationEntityInput'
description 'Represents an entity in SAST CI configuration'
argument :field, GraphQL::STRING_TYPE, required: true,
description: 'CI keyword of entity'
argument :default_value, GraphQL::STRING_TYPE, required: true,
description: 'Default value that is used if value is empty'
argument :value, GraphQL::STRING_TYPE, required: true,
description: 'Current value of the entity'
end
end
end
end
# frozen_string_literal: true
module Types
module CiConfiguration
module Sast
class InputType < BaseInputObject # rubocop:disable Graphql/AuthorizeTypes
graphql_name 'SastCiConfigurationInput'
description 'Represents a CI configuration of SAST'
argument :global, [::Types::CiConfiguration::Sast::EntityInputType],
description: 'List of global entities related to SAST configuration',
required: false
argument :pipeline, [::Types::CiConfiguration::Sast::EntityInputType],
description: 'List of pipeline entities related to SAST configuration',
required: false
end
end
end
end
...@@ -15,6 +15,8 @@ module Security ...@@ -15,6 +15,8 @@ module Security
if result[:status] == :success if result[:status] == :success
result[:success_path] = successful_change_path result[:success_path] = successful_change_path
else
result[:errors] = result[:message]
end end
result result
......
---
title: Change the configureSast mutation to use actual GraphQL types instead of JSON types
merge_request: 40637
author:
type: changed
---
name: security_sast_configuration
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40637
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235929
group: group::static analysis
type: development
default_enabled: false
...@@ -10,16 +10,16 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do ...@@ -10,16 +10,16 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
let_it_be(:service_result_json) do let_it_be(:service_result_json) do
{ {
result: "3ec1b6e9a1f564da65f7c084e2497cf930dfa1c7",
status: "success", status: "success",
success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?" success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?",
errors: nil
} }
end end
let_it_be(:service_error_result_json) do let_it_be(:service_error_result_json) do
{ {
result: "3ec1b6e9a1f564da65f7c084e2497cf930dfa1c7",
status: "error", status: "error",
success_path: nil,
errors: %w(error1 error2) errors: %w(error1 error2)
} }
end end
...@@ -32,12 +32,16 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do ...@@ -32,12 +32,16 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
) )
end end
before do
stub_feature_flags(security_sast_configuration: true)
end
specify { expect(described_class).to require_graphql_authorizations(:push_code) } specify { expect(described_class).to require_graphql_authorizations(:push_code) }
describe '#resolve' do describe '#resolve' do
subject { mutation.resolve(project_path: project.full_path, configuration: {}) } subject { mutation.resolve(project_path: project.full_path, configuration: {}) }
let(:result) { subject[:result] } let(:result) { subject }
it 'raises an error if the resource is not accessible to the user' do it 'raises an error if the resource is not accessible to the user' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
...@@ -77,11 +81,19 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do ...@@ -77,11 +81,19 @@ RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast do
expect(result).to match( expect(result).to match(
status: 'success', status: 'success',
success_path: service_result_json[:success_path], success_path: service_result_json[:success_path],
errors: be_nil errors: []
) )
end end
end end
context 'when sast configuration feature is not enabled' do
it 'raises an exception' do
stub_feature_flags(security_sast_configuration: false)
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when service can not generate any path to create a new merge request' do context 'when service can not generate any path to create a new merge request' do
it 'returns an array of errors' do it 'returns an array of errors' do
allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service|
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationEntityInput'] do
it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntityInput') }
it { expect(described_class.arguments.keys).to match_array(%w[field defaultValue value]) }
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['SastCiConfigurationInput'] do
it { expect(described_class.graphql_name).to eq('SastCiConfigurationInput') }
it { expect(described_class.arguments.keys).to match_array(%w[global pipeline]) }
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