diff --git a/ontrack-extension-auto-versioning/build.gradle.kts b/ontrack-extension-auto-versioning/build.gradle.kts index fa75814c3d..bc11bdaa6c 100644 --- a/ontrack-extension-auto-versioning/build.gradle.kts +++ b/ontrack-extension-auto-versioning/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation("org.apache.commons:commons-lang3") implementation("jakarta.annotation:jakarta.annotation-api") implementation(project(":ontrack-extension-notifications")) + implementation(project(":ontrack-extension-workflows")) implementation("cc.ekblad:4koma:1.2.0") @@ -31,6 +32,7 @@ dependencies { testImplementation(project(path = ":ontrack-extension-general", configuration = "tests")) testImplementation(project(path = ":ontrack-extension-casc", configuration = "tests")) testImplementation(project(path = ":ontrack-extension-notifications", configuration = "tests")) + testImplementation(project(path = ":ontrack-extension-workflows", configuration = "tests")) testImplementation(project(path = ":ontrack-model", configuration = "tests")) testImplementation(project(":ontrack-it-utils")) diff --git a/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutor.kt b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutor.kt new file mode 100644 index 0000000000..509b69e70c --- /dev/null +++ b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutor.kt @@ -0,0 +1,65 @@ +package net.nemerosa.ontrack.extension.av.workflows + +import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.av.AutoVersioningExtensionFeature +import net.nemerosa.ontrack.extension.av.dispatcher.AutoVersioningOrder +import net.nemerosa.ontrack.extension.av.processing.AutoVersioningProcessingService +import net.nemerosa.ontrack.extension.workflows.engine.WorkflowInstance +import net.nemerosa.ontrack.extension.workflows.execution.AbstractTypedWorkflowNodeExecutor +import net.nemerosa.ontrack.extension.workflows.execution.WorkflowNodeExecutorResult +import org.springframework.stereotype.Component +import java.util.* + +@Component +class AutoVersioningWorkflowNodeExecutor( + extensionFeature: AutoVersioningExtensionFeature, + private val autoVersioningProcessingService: AutoVersioningProcessingService, +) : AbstractTypedWorkflowNodeExecutor( + feature = extensionFeature, + id = "auto-versioning", + displayName = "Auto-versioning of a given branch", + dataType = AutoVersioningWorkflowNodeExecutorData::class, +) { + + override fun execute( + workflowInstance: WorkflowInstance, + data: AutoVersioningWorkflowNodeExecutorData, + workflowNodeExecutorResultFeedback: (output: JsonNode?) -> Unit + ): WorkflowNodeExecutorResult { + val order = createOrder(workflowInstance, data) + val outcome = autoVersioningProcessingService.process(order) + TODO("Checks the outcome") + } + + private fun createOrder( + workflowInstance: WorkflowInstance, + data: AutoVersioningWorkflowNodeExecutorData, + ): AutoVersioningOrder { + return AutoVersioningOrder( + uuid = UUID.randomUUID().toString(), + sourceProject = TODO(), + sourceBuildId = null, + sourcePromotionRunId = null, + sourcePromotion = null, + sourceBackValidation = null, + branch = TODO(), + targetPath = data.targetPath, + targetRegex = data.targetRegex, + targetProperty = data.targetProperty, + targetPropertyRegex = data.targetPropertyRegex, + targetPropertyType = data.targetPropertyType, + targetVersion = data.targetVersion, + autoApproval = data.autoApproval, + upgradeBranchPattern = data.upgradeBranchPattern, + postProcessing = data.postProcessing, + postProcessingConfig = data.postProcessingConfig, + validationStamp = data.validationStamp, + autoApprovalMode = data.autoApprovalMode, + reviewers = data.reviewers, + prTitleTemplate = data.prTitleTemplate, + prBodyTemplate = data.prBodyTemplate, + prBodyTemplateFormat = data.prBodyTemplateFormat, + additionalPaths = data.additionalPaths, + ) + } +} diff --git a/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorData.kt b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorData.kt new file mode 100644 index 0000000000..65217bd868 --- /dev/null +++ b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorData.kt @@ -0,0 +1,28 @@ +package net.nemerosa.ontrack.extension.av.workflows + +import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.av.config.AutoApprovalMode +import net.nemerosa.ontrack.extension.av.config.AutoVersioningSourceConfig +import net.nemerosa.ontrack.extension.av.config.AutoVersioningSourceConfigPath + +data class AutoVersioningWorkflowNodeExecutorData( + val targetProject: String, + val targetBranch: String, + val targetVersion: String, + val targetPath: String, + val targetRegex: String? = null, + val targetProperty: String? = null, + val targetPropertyRegex: String? = null, + val targetPropertyType: String? = null, + val autoApproval: Boolean = true, + val upgradeBranchPattern: String = AutoVersioningSourceConfig.DEFAULT_UPGRADE_BRANCH_PATTERN, + val postProcessing: String? = null, + val postProcessingConfig: JsonNode? = null, + val validationStamp: String? = null, + val autoApprovalMode: AutoApprovalMode = AutoApprovalMode.DEFAULT_AUTO_APPROVAL_MODE, + val reviewers: List = emptyList(), + val prTitleTemplate: String? = null, + val prBodyTemplate: String? = null, + val prBodyTemplateFormat: String? = null, + val additionalPaths: List = emptyList(), +) diff --git a/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorOutput.kt b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorOutput.kt new file mode 100644 index 0000000000..c9e5cd142d --- /dev/null +++ b/ontrack-extension-auto-versioning/src/main/java/net/nemerosa/ontrack/extension/av/workflows/AutoVersioningWorkflowNodeExecutorOutput.kt @@ -0,0 +1,5 @@ +package net.nemerosa.ontrack.extension.av.workflows + +data class AutoVersioningWorkflowNodeExecutorOutput( + val autoVersioningOrderId: String, +) diff --git a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/workflows/executors/SlotPipelineCreationWorkflowNodeExecutor.kt b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/workflows/executors/SlotPipelineCreationWorkflowNodeExecutor.kt index 7011d42b39..2999b0ca4e 100644 --- a/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/workflows/executors/SlotPipelineCreationWorkflowNodeExecutor.kt +++ b/ontrack-extension-environments/src/main/java/net/nemerosa/ontrack/extension/environments/workflows/executors/SlotPipelineCreationWorkflowNodeExecutor.kt @@ -1,7 +1,6 @@ package net.nemerosa.ontrack.extension.environments.workflows.executors import com.fasterxml.jackson.databind.JsonNode -import net.nemerosa.ontrack.extension.casc.context.ConfigContext import net.nemerosa.ontrack.extension.environments.EnvironmentsExtensionFeature import net.nemerosa.ontrack.extension.environments.Slot import net.nemerosa.ontrack.extension.environments.service.EnvironmentService @@ -28,7 +27,6 @@ class SlotPipelineCreationWorkflowNodeExecutor( private val environmentService: EnvironmentService, private val securityService: SecurityService, private val structureService: StructureService, - private val configContext: ConfigContext, ) : AbstractExtension(extensionFeature), WorkflowNodeExecutor { override val id: String = "slot-pipeline-creation" diff --git a/ontrack-extension-workflows/src/main/java/net/nemerosa/ontrack/extension/workflows/execution/AbstractTypedWorkflowNodeExecutor.kt b/ontrack-extension-workflows/src/main/java/net/nemerosa/ontrack/extension/workflows/execution/AbstractTypedWorkflowNodeExecutor.kt new file mode 100644 index 0000000000..bd2e8601fb --- /dev/null +++ b/ontrack-extension-workflows/src/main/java/net/nemerosa/ontrack/extension/workflows/execution/AbstractTypedWorkflowNodeExecutor.kt @@ -0,0 +1,35 @@ +package net.nemerosa.ontrack.extension.workflows.execution + +import com.fasterxml.jackson.databind.JsonNode +import net.nemerosa.ontrack.extension.workflows.engine.WorkflowInstance +import net.nemerosa.ontrack.json.parseInto +import net.nemerosa.ontrack.model.extension.ExtensionFeature +import kotlin.reflect.KClass + +/** + * [WorkflowNodeExecutor] implementation relying on known types for its data & output. + * + * @param D Type for the data + */ +abstract class AbstractTypedWorkflowNodeExecutor( + override val feature: ExtensionFeature, + override val id: String, + override val displayName: String, + private val dataType: KClass, +) : WorkflowNodeExecutor { + override suspend fun execute( + workflowInstance: WorkflowInstance, + workflowNodeId: String, + workflowNodeExecutorResultFeedback: (output: JsonNode?) -> Unit + ): WorkflowNodeExecutorResult { + val data = workflowInstance.workflow.getNode(workflowNodeId).data.parseInto(dataType) + return execute(workflowInstance, data, workflowNodeExecutorResultFeedback) + } + + abstract fun execute( + workflowInstance: WorkflowInstance, + data: D, + workflowNodeExecutorResultFeedback: (output: JsonNode?) -> Unit, + ): WorkflowNodeExecutorResult + +} \ No newline at end of file diff --git a/ontrack-kdsl-acceptance/src/test/java/net/nemerosa/ontrack/kdsl/acceptance/tests/av/ACCAutoVersioningWorkflow.kt b/ontrack-kdsl-acceptance/src/test/java/net/nemerosa/ontrack/kdsl/acceptance/tests/av/ACCAutoVersioningWorkflow.kt new file mode 100644 index 0000000000..f78bd106f0 --- /dev/null +++ b/ontrack-kdsl-acceptance/src/test/java/net/nemerosa/ontrack/kdsl/acceptance/tests/av/ACCAutoVersioningWorkflow.kt @@ -0,0 +1,87 @@ +package net.nemerosa.ontrack.kdsl.acceptance.tests.av + +import net.nemerosa.ontrack.kdsl.acceptance.tests.scm.withMockScmRepository +import net.nemerosa.ontrack.kdsl.acceptance.tests.support.uid +import net.nemerosa.ontrack.kdsl.acceptance.tests.support.waitUntil +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.type.SlotPipelineStatus +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.type.SlotWorkflowTrigger +import net.nemerosa.ontrack.kdsl.spec.extension.environments.environments +import net.nemerosa.ontrack.kdsl.spec.extension.environments.workflows.addWorkflow +import org.junit.jupiter.api.Test + +class ACCAutoVersioningWorkflow : AbstractACCAutoVersioningTestSupport() { + + @Test + fun `Deployment workflow triggering an auto-versioning followed by the change of state to deployed`() { + // Creating an application project + val application = project { this } + + // Creating an environment + val environment = ontrack.environments.createEnvironment( + name = uid("env-"), + order = 0, + ) + // Creating a slot for the environment & the project + val slot = environment.createSlot( + project = application, + ) + + // Creating a GitOps project targeted by the deployment + withMockScmRepository(ontrack) { + withAutoVersioning { + repositoryFile("gradle.properties") { "version = 0.0.1" } + + project { + val gitOps = this + branch { + val gitOpsBranch = this + configuredForMockRepository() + + // Adding a workflow to this slot + slot.addWorkflow( + trigger = SlotWorkflowTrigger.DEPLOYING, + workflowYaml = """ + name: Deployment + nodes: + - id: av + executorId: auto-versioning + data: + targetProject: ${gitOps.name} + targetBranch ${gitOpsBranch.name} + targetPath: gradle.properties + targetProperty: version + targetVersion: ${'$'}{build} + - id: deployed + parents: + - id: av + executorId: slot-pipeline-deployed + data: {} + """.trimIndent() + ) + + // Creating a build for the application project + val build = build(name = "1.0.0") { this } + // Creating a pipeline for this build & starting its deployment + val pipeline = slot.createPipeline(build = build) + pipeline.startDeploying() + + // We expect the pipeline to become deployed + waitUntil( + task = "Pipeline deployed", + timeout = 10_000, + interval = 1_000, + ) { + ontrack.environments.findPipelineById(pipeline.id)?.status == SlotPipelineStatus.DEPLOYED + } + + // We expect the GitOps repository to contain the new version + assertThatMockScmRepository { + fileContains("gradle.properties") { "version = 1.0.0" } + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateEnvironment.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateEnvironment.graphql new file mode 100644 index 0000000000..9ff94b9c9c --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateEnvironment.graphql @@ -0,0 +1,18 @@ +mutation CreateEnvironment( + $name: String!, + $order: Int!, + $description: String! = "", + $tags: [String!]! = [], +) { + createEnvironment(input: { + name: $name, + order: $order, + description: $description, + tags: $tags, + }) { + ...PayloadUserErrors + environment { + id + } + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreatePipeline.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreatePipeline.graphql new file mode 100644 index 0000000000..45839d0bd6 --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreatePipeline.graphql @@ -0,0 +1,16 @@ +mutation CreatePipeline( + $slotId: String!, + $buildId: Int!, +) { + startSlotPipeline(input: { + slotId: $slotId, + buildId: $buildId, + }) { + ...PayloadUserErrors + pipeline { + id + number + status + } + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlot.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlot.graphql new file mode 100644 index 0000000000..1d48f9830e --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlot.graphql @@ -0,0 +1,20 @@ +mutation CreateSlot( + $environmentId: String!, + $projectId: Int!, + $qualifier: String! = "", + $description: String = null, +) { + createSlots(input: { + environmentIds: [$environmentId], + projectId: $projectId, + qualifier: $qualifier, + description: $description, + }) { + ...PayloadUserErrors + slots { + slots { + id + } + } + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlotWorkflow.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlotWorkflow.graphql new file mode 100644 index 0000000000..00a4df6ce7 --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/CreateSlotWorkflow.graphql @@ -0,0 +1,13 @@ +mutation CreateSlotWorkflow( + $slotId: String!, + $trigger: SlotWorkflowTrigger!, + $workflowYaml: String!, +) { + addSlotWorkflow(input: { + slotId: $slotId, + trigger: $trigger, + workflowYaml: $workflowYaml, + }) { + ...PayloadUserErrors + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/EnvironmentFragment.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/EnvironmentFragment.graphql new file mode 100644 index 0000000000..ea7a8806fd --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/EnvironmentFragment.graphql @@ -0,0 +1,7 @@ +fragment EnvironmentFragment on Environment { + id + name + order + description + tags +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/FindPipelineById.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/FindPipelineById.graphql new file mode 100644 index 0000000000..3a268adcef --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/FindPipelineById.graphql @@ -0,0 +1,7 @@ +query FindPipelineById( + $id: String!, +) { + slotPipelineById(id: $id) { + ...SlotPipelineFragment + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotFragment.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotFragment.graphql new file mode 100644 index 0000000000..ae4aee2e52 --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotFragment.graphql @@ -0,0 +1,11 @@ +fragment SlotFragment on Slot { + id + description + project { + ...ProjectFragment + } + qualifier + environment { + ...EnvironmentFragment + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotPipelineFragment.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotPipelineFragment.graphql new file mode 100644 index 0000000000..f9dc3002d8 --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/SlotPipelineFragment.graphql @@ -0,0 +1,11 @@ +fragment SlotPipelineFragment on SlotPipeline { + id + number + status + build { + ...BuildFragment + } + slot { + ...SlotFragment + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/StartDeployingPipeline.graphql b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/StartDeployingPipeline.graphql new file mode 100644 index 0000000000..737752d580 --- /dev/null +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/environments/StartDeployingPipeline.graphql @@ -0,0 +1,12 @@ +mutation StartDeployingPipeline( + $pipelineId: String!, +) { + startSlotPipelineDeployment(input: { + pipelineId: $pipelineId, + }) { + ...PayloadUserErrors + deploymentStatus { + status + } + } +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/schema.json b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/schema.json index e1048658f4..e9203eadd9 100644 --- a/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/schema.json +++ b/ontrack-kdsl/src/main/graphql/net/nemerosa/ontrack/kdsl/connector/graphql/schema/schema.json @@ -6631,8 +6631,8 @@ "description": "Information about the promotions of a build", "fields": [ { - "name": "noPromotionItems", - "description": "Items not linked to any promotion", + "name": "items", + "description": "items field", "args": [], "type": { "kind": "NON_NULL", @@ -6644,7 +6644,7 @@ "kind": "NON_NULL", "name": null, "ofType": { - "kind": "UNION", + "kind": "OBJECT", "name": "BuildPromotionInfoItem" } } @@ -6652,25 +6652,41 @@ }, "isDeprecated": false, "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "BuildPromotionInfoItem", + "description": "Information about the promotions of a build", + "fields": [ + { + "name": "promotionLevel", + "description": "promotionLevel field", + "args": [], + "type": { + "kind": "OBJECT", + "name": "PromotionLevel", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null }, { - "name": "withPromotionItems", - "description": "withPromotionItems field", + "name": "data", + "description": "Item's data", "args": [], "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "LinkedBuildPromotionInfoItems" - } - } + "kind": "UNION", + "name": "BuildPromotionInfoItemData", + "ofType": null } }, "isDeprecated": false, @@ -6684,7 +6700,7 @@ }, { "kind": "UNION", - "name": "BuildPromotionInfoItem", + "name": "BuildPromotionInfoItemData", "description": "Information about the promotion of a build", "fields": null, "inputFields": null, @@ -15048,6 +15064,64 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "DeleteSlotWorkflowInput", + "description": "Input type for the deleteSlotWorkflow mutation.", + "fields": null, + "inputFields": [ + { + "name": "id", + "description": "id field", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "DeleteSlotWorkflowPayload", + "description": "Output type for the deleteSlotWorkflow mutation.", + "fields": [ + { + "name": "errors", + "description": "List of errors", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UserError", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Payload", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "DeleteSubscriptionInput", @@ -26243,56 +26317,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "LinkedBuildPromotionInfoItems", - "description": "Information linked to promotion levels", - "fields": [ - { - "name": "promotionLevel", - "description": "promotionLevel field", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "PromotionLevel", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "items", - "description": "Items linked to this promotion", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "UNION", - "name": "BuildPromotionInfoItem" - } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "LinksBuildInput", @@ -32377,6 +32401,52 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "saveSlotWorkflow", + "description": "Add or saves a workflow into an existing slot", + "args": [ + { + "name": "input", + "description": "Input for the mutation", + "type": { + "kind": "INPUT_OBJECT", + "name": "SaveSlotWorkflowInput", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "SaveSlotWorkflowPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deleteSlotWorkflow", + "description": "Deletes a workflow from a slot", + "args": [ + { + "name": "input", + "description": "Input for the mutation", + "type": { + "kind": "INPUT_OBJECT", + "name": "DeleteSlotWorkflowInput", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "DeleteSlotWorkflowPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "validateJsonWorkflow", "description": "Validates a workflow which is defined as JSON", @@ -34118,6 +34188,11 @@ "name": "DeleteSlotAdmissionRuleConfigPayload", "ofType": null }, + { + "kind": "OBJECT", + "name": "DeleteSlotWorkflowPayload", + "ofType": null + }, { "kind": "OBJECT", "name": "DeleteSubscriptionPayload", @@ -34363,6 +34438,11 @@ "name": "SaveSlotAdmissionRuleConfigPayload", "ofType": null }, + { + "kind": "OBJECT", + "name": "SaveSlotWorkflowPayload", + "ofType": null + }, { "kind": "OBJECT", "name": "SaveYamlWorkflowPayload", @@ -45462,6 +45542,114 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "SaveSlotWorkflowInput", + "description": "Input type for the saveSlotWorkflow mutation.", + "fields": null, + "inputFields": [ + { + "name": "id", + "description": "id field", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "slotId", + "description": "slotId field", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "trigger", + "description": "trigger field", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SlotWorkflowTrigger", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "workflow", + "description": "workflow field", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "JSON", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SaveSlotWorkflowPayload", + "description": "Output type for the saveSlotWorkflow mutation.", + "fields": [ + { + "name": "slotWorkflow", + "description": "Created or updated slot workflow", + "args": [], + "type": { + "kind": "OBJECT", + "name": "SlotWorkflow", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "errors", + "description": "List of errors", + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "UserError", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [ + { + "kind": "INTERFACE", + "name": "Payload", + "ofType": null + } + ], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "SaveYamlWorkflowInput", @@ -55834,6 +56022,29 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "workflows", + "description": "List of workflows for this slot", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "SlotWorkflow" + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Environment.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Environment.kt new file mode 100644 index 0000000000..07a00aa561 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Environment.kt @@ -0,0 +1,45 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import com.apollographql.apollo.api.Input +import net.nemerosa.ontrack.kdsl.connector.Connector +import net.nemerosa.ontrack.kdsl.connector.graphql.convert +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.CreateSlotMutation +import net.nemerosa.ontrack.kdsl.connector.graphqlConnector +import net.nemerosa.ontrack.kdsl.spec.Project +import net.nemerosa.ontrack.kdsl.spec.Resource + +class Environment( + connector: Connector, + val id: String, + val name: String, + val order: Int, + val description: String?, + val tags: List = emptyList(), +) : Resource(connector) { + + fun createSlot( + project: Project, + qualifier: String = "", + description: String = "", + ): Slot { + val slot = graphqlConnector.mutate( + CreateSlotMutation( + this.id, + project.id.toInt(), + qualifier, + Input.optional(description) + ) + ) { it?.createSlots()?.fragments()?.payloadUserErrors()?.convert() } + ?.createSlots()?.slots()?.slots()?.firstOrNull() + ?: error("Cannot get created slot") + return Slot( + connector = connector, + id = slot.id(), + environment = this, + project = project, + qualifier = qualifier, + description = description, + ) + } + +} diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentExtensions.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentExtensions.kt new file mode 100644 index 0000000000..6c774dfa03 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentExtensions.kt @@ -0,0 +1,13 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connected +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.fragment.EnvironmentFragment + +fun EnvironmentFragment.toEnvironment(connected: Connected) = Environment( + connector = connected.connector, + id = id(), + name = name(), + order = order()!!, + description = description(), + tags = tags(), +) diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsMgt.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsMgt.kt new file mode 100644 index 0000000000..dd7b2f708c --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsMgt.kt @@ -0,0 +1,43 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connected +import net.nemerosa.ontrack.kdsl.connector.Connector +import net.nemerosa.ontrack.kdsl.connector.graphql.convert +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.CreateEnvironmentMutation +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.FindPipelineByIdQuery +import net.nemerosa.ontrack.kdsl.connector.graphqlConnector + +class EnvironmentsMgt(connector: Connector) : Connected(connector) { + + fun createEnvironment( + name: String, + order: Int, + description: String = "", + tags: List = emptyList(), + ): Environment { + val environment = graphqlConnector.mutate( + CreateEnvironmentMutation( + name, + order, + description, + tags, + ) + ) { it?.createEnvironment()?.fragments()?.payloadUserErrors()?.convert() } + ?.createEnvironment()?.environment() + ?: error("Cannot get created environment") + return Environment( + connector = connector, + id = environment.id(), + name = name, + order = order, + description = description, + ) + } + + fun findPipelineById(id: String): SlotPipeline? = + graphqlConnector.query( + FindPipelineByIdQuery(id) + )?.slotPipelineById()?.fragments() + ?.slotPipelineFragment()?.toPipeline(this) + +} \ No newline at end of file diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsOntrackExtensions.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsOntrackExtensions.kt new file mode 100644 index 0000000000..ce7b488a35 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/EnvironmentsOntrackExtensions.kt @@ -0,0 +1,8 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.spec.Ontrack + +/** + * Management of environments, slots & pipelines in Ontrack. + */ +val Ontrack.environments: EnvironmentsMgt get() = EnvironmentsMgt(connector) diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Slot.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Slot.kt new file mode 100644 index 0000000000..7b8ce6effb --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/Slot.kt @@ -0,0 +1,39 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connector +import net.nemerosa.ontrack.kdsl.connector.graphql.convert +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.CreatePipelineMutation +import net.nemerosa.ontrack.kdsl.connector.graphqlConnector +import net.nemerosa.ontrack.kdsl.spec.Build +import net.nemerosa.ontrack.kdsl.spec.Project +import net.nemerosa.ontrack.kdsl.spec.Resource + +class Slot( + connector: Connector, + val id: String, + val environment: Environment, + val project: Project, + val qualifier: String = "", + val description: String = "", +) : Resource(connector) { + + fun createPipeline(build: Build): SlotPipeline { + val pipeline = graphqlConnector.mutate( + CreatePipelineMutation( + id, + build.id.toInt(), + ) + ) { it?.startSlotPipeline()?.fragments()?.payloadUserErrors()?.convert() } + ?.startSlotPipeline()?.pipeline() + ?: error("Cannot get the create pipeline") + return SlotPipeline( + connector = connector, + id = pipeline.id(), + number = pipeline.number() ?: 1, + slot = this, + build = build, + status = pipeline.status(), + ) + } + +} diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotExtensions.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotExtensions.kt new file mode 100644 index 0000000000..541add6e57 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotExtensions.kt @@ -0,0 +1,14 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connected +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.fragment.SlotFragment +import net.nemerosa.ontrack.kdsl.spec.toProject + +fun SlotFragment.toSlot(connected: Connected) = Slot( + connector = connected.connector, + id = id(), + environment = environment().fragments().environmentFragment().toEnvironment(connected), + project = project().fragments().projectFragment().toProject(connected), + qualifier = qualifier(), + description = description() ?: "", +) \ No newline at end of file diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipeline.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipeline.kt new file mode 100644 index 0000000000..bf3f5e2c13 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipeline.kt @@ -0,0 +1,31 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connector +import net.nemerosa.ontrack.kdsl.connector.graphql.convert +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.StartDeployingPipelineMutation +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.type.SlotPipelineStatus +import net.nemerosa.ontrack.kdsl.connector.graphqlConnector +import net.nemerosa.ontrack.kdsl.spec.Build +import net.nemerosa.ontrack.kdsl.spec.Resource + +class SlotPipeline( + connector: Connector, + val id: String, + val number: Int, + val slot: Slot, + val build: Build, + val status: SlotPipelineStatus, +) : Resource(connector) { + + fun startDeploying() { + val status = graphqlConnector.mutate( + StartDeployingPipelineMutation( + id + ) + ) { it?.startSlotPipelineDeployment()?.fragments()?.payloadUserErrors()?.convert() } + ?.startSlotPipelineDeployment()?.deploymentStatus()?.status() + ?: error("Cannot get the deployment status") + if (!status) error("Deployment could not be started") + } + +} diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipelineExtensions.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipelineExtensions.kt new file mode 100644 index 0000000000..20d5469586 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/SlotPipelineExtensions.kt @@ -0,0 +1,14 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments + +import net.nemerosa.ontrack.kdsl.connector.Connected +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.fragment.SlotPipelineFragment +import net.nemerosa.ontrack.kdsl.spec.toBuild + +fun SlotPipelineFragment.toPipeline(connected: Connected) = SlotPipeline( + connector = connected.connector, + id = id(), + number = number()!!, + status = status(), + slot = slot().fragments().slotFragment().toSlot(connected), + build = build().fragments().buildFragment().toBuild(connected), +) diff --git a/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/workflows/SlotWorkflowExtensions.kt b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/workflows/SlotWorkflowExtensions.kt new file mode 100644 index 0000000000..f6ab9a4509 --- /dev/null +++ b/ontrack-kdsl/src/main/java/net/nemerosa/ontrack/kdsl/spec/extension/environments/workflows/SlotWorkflowExtensions.kt @@ -0,0 +1,20 @@ +package net.nemerosa.ontrack.kdsl.spec.extension.environments.workflows + +import net.nemerosa.ontrack.kdsl.connector.graphql.convert +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.environments.CreateSlotWorkflowMutation +import net.nemerosa.ontrack.kdsl.connector.graphql.schema.type.SlotWorkflowTrigger +import net.nemerosa.ontrack.kdsl.connector.graphqlConnector +import net.nemerosa.ontrack.kdsl.spec.extension.environments.Slot + +fun Slot.addWorkflow( + trigger: SlotWorkflowTrigger, + workflowYaml: String, +) { + graphqlConnector.mutate( + CreateSlotWorkflowMutation( + id, + trigger, + workflowYaml + ) + ) { it?.addSlotWorkflow()?.fragments()?.payloadUserErrors()?.convert() } +} \ No newline at end of file