Skip to content

Commit

Permalink
Merge pull request #11 from Liftric/feat/add_create_project_task
Browse files Browse the repository at this point in the history
feat: add createProject Task
  • Loading branch information
Khartris committed Feb 1, 2024
2 parents b690a92 + 149d8ce commit 87a79a7
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 9 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This plugin internally applies the [CycloneDX Gradle plugin](https://github.com/
The plugin offers several tasks:

- `runDepTrackWorkflow`: Runs `generateSbom`, `uploadSbom`, `generateVex`, `uploadVex` and `riskScore` tasks for CI/CD.
- `createProject`: Creates a Project
- `generateSbom`: Generates the SBOM (Runs "cyclonedxBom" from [cyclonedx-gradle-plugin](https://github.com/CycloneDX/cyclonedx-gradle-plugin) under the hood)
- `uploadSbom`: Uploads SBOM file.
- `generateVex`: Generates VEX file.
Expand All @@ -20,6 +21,17 @@ The plugin offers several tasks:

Each task requires certain inputs which are to be specified in your `build.gradle.kts`. The configuration for each task is as follows:

#### createProject

- `url`: Dependency Track API URL
- `apiKey`: Dependency Track API KEY
- `projectName`: The Name of the Project you want to create
- `projectVersion`: *Optional* - The Version of the Project you want to create
- `projectActive`: *Optional* - default is true, set to false to create an inactive Project
- `projectTags`: *Optional* - add Tags to your Project
- `parentUUID`: *Optional* - Used for creating in a parent project
- `ignoreProjectAlreadyExists`: *Optional* - default is false, set to true to ignore "Project already exist" error

#### uploadSbom

- `url`: Dependency Track API URL
Expand Down
72 changes: 72 additions & 0 deletions src/integrationTest/kotlin/com/liftric/dtcp/CreateProjectTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.liftric.dtcp

import com.liftric.dtcp.service.ApiService
import org.gradle.testkit.runner.GradleRunner
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.io.File
import kotlinx.coroutines.runBlocking
import org.gradle.testkit.runner.UnexpectedBuildFailure

class CreateProjectTest : IntegrationTestBase() {
@Test
fun testCreateProjectTest() {
val projectName = "createProjectTest"
val version = "1.0.0"

val apiService = ApiService(dependencyTrackApiEndpoint)

val dependencyTrackAccessKey =
runBlocking { apiService.getDependencyTrackAccessKey() }

assertTrue(dependencyTrackAccessKey.isNotEmpty())

val projectDir = File("build/createProjectTest")

projectDir.mkdirs()
projectDir.resolve("settings.gradle.kts").writeText("")
projectDir.resolve("build.gradle.kts").writeText(
"""
import com.liftric.dtcp.extensions.*
plugins {
kotlin("jvm") version "1.8.21"
id("com.liftric.dependency-track-companion-plugin")
}
repositories {
mavenCentral()
}
group = "com.liftric.$projectName"
version = "$version"
dependencyTrackCompanion {
url.set("$dependencyTrackApiEndpoint")
apiKey.set("$dependencyTrackAccessKey")
projectName.set("$projectName")
projectVersion.set("$version")
projectActive.set(false)
}
"""
)

/**
* GradleRunner fails under the hood, but the Project is created successfully.
* see [IgnoreErrorApiService] for more info.
* */
try {
GradleRunner
.create()
.withProjectDir(projectDir)
.withArguments("build", "createProject")
.withPluginClasspath().build()
} catch (e: UnexpectedBuildFailure) {
assertTrue(e.message!!.contains("/api/v1/project: 500 Server Error"))
}

runBlocking {
assertTrue(apiService.verifyProjectCreation(dependencyTrackAccessKey, projectName, version))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package com.liftric.dtcp

import com.liftric.dtcp.model.VexComponent
import com.liftric.dtcp.model.VexVulnerability
import com.liftric.dtcp.service.ApiService
import com.liftric.dtcp.service.IgnoreErrorApiService
import org.gradle.testkit.runner.GradleRunner
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import kotlinx.coroutines.runBlocking

/**
Expand All @@ -25,7 +20,7 @@ import kotlinx.coroutines.runBlocking
class RunDepTrackWorkflowTest: IntegrationTestBase() {
@Test
fun testRunDepTrackWorkflowTest() {
val projectName = "dtTest"
val projectName = "runDepTrackWorkflowTest"
val version = "1.0.0"

val dependencyTrackAccessKey =
Expand Down
12 changes: 12 additions & 0 deletions src/integrationTest/kotlin/com/liftric/dtcp/service/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

Expand All @@ -31,4 +32,15 @@ class ApiService(private val dependencyTrackApiEndpoint: String) {
val response: KeyResponse = client.put("$dependencyTrackApiEndpoint/api/v1/team/$adminUuid/key").body()
return response.key
}

suspend fun verifyProjectCreation(dependencyTrackAccessKey: String, name: String, version: String): Boolean {
val response: HttpResponse =
client.get("${dependencyTrackApiEndpoint}/api/v1/project/lookup?name=$name&version=$version") {
headers {
append("X-Api-Key", dependencyTrackAccessKey)
append("Content-Type", "application/json")
}
}
return response.status.value == 200
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class IgnoreErrorApiService(

client.put("${dependencyTrackApiEndpoint}/api/v1/project") {
headers {
append("X-Api-Key", "$dependencyTrackAccessKey")
append("X-Api-Key", dependencyTrackAccessKey)
append("Content-Type", "application/json")
}
setBody(Json.encodeToString(projectData))
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/com/liftric/dtcp/DepTrackCompanionPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ class DepTrackCompanionPlugin : Plugin<Project> {
)
extension.autoCreate.convention(false)

val createProject = project.tasks.register("createProject", CreateProject::class.java) { task ->
task.group = taskGroup
task.description = "Creates a project"
task.url.set(extension.url)
task.apiKey.set(extension.apiKey)
task.projectActive.set(extension.projectActive)
task.projectTags.set(extension.projectTags)
task.projectName.set(extension.projectName)
task.projectVersion.set(extension.projectVersion)
task.parentUUID.set(extension.parentUUID)
task.ignoreProjectAlreadyExists.set(extension.ignoreProjectAlreadyExists)
}

val generateSbom = project.tasks.register("generateSbom") { task ->
task.group = taskGroup
task.description = "Generate SBOM file"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.liftric.dtcp.extensions

import com.liftric.dtcp.model.ProjectTag
import org.gradle.api.Project
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
Expand All @@ -16,9 +17,12 @@ abstract class DepTrackCompanionExtension(val project: Project) {
abstract val projectUUID: Property<String>
abstract val projectName: Property<String>
abstract val projectVersion: Property<String>
abstract val projectActive: Property<Boolean>
abstract val projectTags: ListProperty<ProjectTag>
abstract val parentUUID: Property<String>
abstract val parentName: Property<String>
abstract val parentVersion: Property<String>
abstract val ignoreProjectAlreadyExists: Property<Boolean>

abstract val riskScoreData: Property<RiskScoreBuilder>

Expand Down
23 changes: 21 additions & 2 deletions src/main/kotlin/com/liftric/dtcp/model/DependencyTrack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ data class Component(
val version: String,
val purl: String,
val uuid: String,
val repositoryMeta: RepositoryMeta? = null
val repositoryMeta: RepositoryMeta? = null,
)

@Serializable
data class RepositoryMeta(
val latestVersion: String
val latestVersion: String,
)

@Serializable
Expand All @@ -28,6 +28,25 @@ data class Project(
val lastInheritedRiskScore: Double? = null,
)

@Serializable
data class CreateProject(
val name: String,
val version: String? = null,
val active: Boolean,
val tags: List<ProjectTag>,
val parent: Parent? = null,
) {
@Serializable
data class Parent(
val uuid: String? = null,
)
}

@Serializable
data class ProjectTag(
val name: String,
)

@Serializable
data class DirectDependency(
val name: String,
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/com/liftric/dtcp/service/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.ktor.client.statement.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.http.*
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import java.io.File

Expand Down Expand Up @@ -61,4 +62,15 @@ class ApiService(apiKey: String) {
}
}
}

suspend fun <T> putRequest(url: String, body: T, serializer: KSerializer<T>): HttpResponse {
val jsonBody = Json.encodeToString(serializer, body)
return client.put(url) {
headers {
append(HttpHeaders.ContentType, ContentType.Application.Json)
}
contentType(ContentType.Application.Json)
setBody(jsonBody)
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/com/liftric/dtcp/service/DependencyTrack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,9 @@ class DependencyTrack(apiKey: String, private val baseUrl: String) {
} while (response.processing)
println("Analysis is complete.")
}

fun createProject(project: CreateProject) = runBlocking {
val url = "$baseUrl/api/v1/project"
client.putRequest(url, project, CreateProject.serializer())
}
}
66 changes: 66 additions & 0 deletions src/main/kotlin/com/liftric/dtcp/tasks/CreateProject.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.liftric.dtcp.tasks

import com.liftric.dtcp.model.CreateProject
import com.liftric.dtcp.model.ProjectTag
import com.liftric.dtcp.service.DependencyTrack
import org.gradle.api.DefaultTask
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional

abstract class CreateProject : DefaultTask() {
@get:Input
abstract val apiKey: Property<String>

@get:Input
abstract val url: Property<String>

@get:Input
abstract val projectName: Property<String>

@get:Input
@get:Optional
abstract val projectVersion: Property<String>

@get:Input
@get:Optional
abstract val projectActive: Property<Boolean>

@get:Input
@get:Optional
abstract val projectTags: ListProperty<ProjectTag>

@get:Input
@get:Optional
abstract val parentUUID: Property<String>

@get:Input
@get:Optional
abstract val ignoreProjectAlreadyExists: Property<Boolean>

@TaskAction
fun createProjectTask() {
val dt = DependencyTrack(apiKey.get(), url.get())

val project = CreateProject(
name = projectName.get(),
version = projectVersion.orNull,
active = projectActive.orNull ?: true,
tags = projectTags.getOrElse(emptyList()),
parent = parentUUID.orNull?.let { CreateProject.Parent(it) }
)

try {
dt.createProject(project)
} catch (e: Exception) {
if (ignoreProjectAlreadyExists.getOrElse(false) && e.message?.contains("already exists") == true) {
logger.info("Project already exists, ignoring")
return
}
logger.error("Error creating project: ${e.message}")
throw e
}
}
}

0 comments on commit 87a79a7

Please sign in to comment.