Skip to content

Commit dd19f35

Browse files
authored
Merge pull request #23 from zendern/fork-properties
Fork properties
2 parents 4eba2f1 + 186f442 commit dd19f35

File tree

14 files changed

+394
-5
lines changed

14 files changed

+394
-5
lines changed

Diff for: README.md

+49-2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ openApi {
7878
outputDir.set(file("$buildDir/docs"))
7979
outputFileName.set("swagger.json")
8080
waitTimeInSeconds.set(10)
81+
forkProperties.set("-Dspring.profiles.active=special")
8182
}
8283
```
8384

@@ -87,7 +88,53 @@ Parameter | Description | Required | Default
8788
`outputDir` | The output directory for the generated OpenAPI file | No | $buildDir - Your project's build dir
8889
`outputFileName` | The name of the output file with extension | No | openapi.json
8990
`waitTimeInSeconds` | Time to wait in seconds for your Spring Boot application to start, before we make calls to `apiDocsUrl` to download the OpenAPI doc | No | 30 seconds
91+
`forkProperites` | Any system property that you would normal need to start your spring boot application. Can either be a static string or a java Properties object | No | ""
9092

91-
# Building the plugin
93+
### Fork properties examples
94+
Fork properties allows you to send in anything that might be necessary to allow for the forked spring boot application that gets started
95+
to be able to start (profiles, other custom properties, etc etc)
96+
97+
#### Static string
98+
```
99+
openApi {
100+
forkProperties = "-Dspring.profiles.active=special -DstringPassedInForkProperites=true"
101+
}
102+
```
103+
104+
#### Passing straight from gradle
105+
This allows for you to be able to just send in whatever you need when you generate docs.
92106

93-
TODO
107+
`./gradlew clean generateOpenApiDocs -Dspring.profiles.active=special`
108+
109+
and as long as the config looks as follows that value will be passed into the forked spring boot application.
110+
```
111+
openApi {
112+
forkProperties = System.properties
113+
}
114+
```
115+
116+
# Building the plugin
117+
1. Clone the repo `[email protected]:springdoc/springdoc-openapi-gradle-plugin.git`
118+
2. Build and publish the plugin into your local maven repository by running the following
119+
```
120+
./gradlew clean pTML
121+
```
122+
123+
# Testing the plugin
124+
1. Create a new spring boot application or use an existing spring boot app and follow the `How To Use` section above to configure this plugin.
125+
2. Update the version for the plugin to match the current version found in `build.gradle.kts`
126+
127+
```
128+
id("org.springdoc.openapi-gradle-plugin") version "1.33.0-SNAPSHOT"
129+
```
130+
131+
3. Add the following to the spring boot apps `settings.gradle`
132+
133+
```
134+
pluginManagement {
135+
repositories {
136+
mavenLocal()
137+
gradlePluginPortal()
138+
}
139+
}
140+
```

Diff for: build.gradle.kts

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ dependencies {
4141
implementation(group = "com.google.code.gson", name = "gson", version = "2.8.6")
4242
implementation(group = "org.awaitility", name = "awaitility-kotlin", version = "4.0.2")
4343
implementation(files("$projectDir/libs/gradle-processes-0.5.0.jar"))
44+
45+
testImplementation(gradleTestKit())
46+
testImplementation("junit:junit:4.13")
47+
testImplementation("com.beust:klaxon:5.2")
4448
}
4549

4650
gradlePlugin {

Diff for: src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiExtension.kt

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ open class OpenApiExtension @Inject constructor(project: Project) {
1010
val outputFileName: Property<String> = project.objects.property(String::class.java)
1111
val outputDir: DirectoryProperty = project.objects.directoryProperty()
1212
val waitTimeInSeconds: Property<Int> = project.objects.property(Int::class.java)
13+
val forkProperties: Property<Any> = project.objects.property(Any::class.java)
1314
}

Diff for: src/main/kotlin/org/springdoc/openapi/gradle/plugin/OpenApiGradlePlugin.kt

+23-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package org.springdoc.openapi.gradle.plugin
22

33
import com.github.jengelman.gradle.plugins.processes.tasks.Fork
4-
import org.gradle.api.GradleException
54
import org.gradle.api.Plugin
65
import org.gradle.api.Project
7-
import org.slf4j.LoggerFactory
6+
import org.gradle.api.logging.Logging
7+
import java.util.*
88

99
open class OpenApiGradlePlugin : Plugin<Project> {
10+
private val LOGGER =
11+
Logging.getLogger(OpenApiGradlePlugin::class.java)
1012

1113
override fun apply(project: Project) {
1214
// Run time dependency on the following plugins
@@ -19,13 +21,31 @@ open class OpenApiGradlePlugin : Plugin<Project> {
1921
// Spring boot jar task
2022
val bootJarTask = project.tasks.named(SPRING_BOOT_JAR_TASK_NAME)
2123

24+
val extension: OpenApiExtension = project.extensions.run {
25+
getByName(EXTENSION_NAME) as OpenApiExtension
26+
}
27+
2228
// Create a forked version spring boot run task
2329
val forkedSpringBoot = project.tasks.register(FORKED_SPRING_BOOT_RUN_TASK_NAME, Fork::class.java) { fork ->
2430
fork.dependsOn(bootJarTask)
2531

2632
fork.onlyIf {
2733
val bootJar = bootJarTask.get().outputs.files.first()
28-
fork.commandLine = listOf("java", "-jar", "$bootJar")
34+
35+
val command = mutableListOf("java", "-jar")
36+
if (extension.forkProperties.isPresent) {
37+
val element = extension.forkProperties.get()
38+
if (element is String) {
39+
command.add(element)
40+
} else if (element is Properties) {
41+
element.toMap().map { r -> "-D${r.key}=${r.value}" }.forEach { p -> command.add(p) }
42+
} else {
43+
LOGGER.warn("Failed to use the value set for 'forkProprerties'. Only String and Properties objects are supported.")
44+
}
45+
}
46+
command.add("$bootJar")
47+
48+
fork.commandLine = command
2949
true
3050
}
3151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package org.springdoc.openapi.gradle.plugin
2+
3+
import com.beust.klaxon.JsonObject
4+
import com.beust.klaxon.Klaxon
5+
import com.beust.klaxon.Parser
6+
import org.gradle.internal.impldep.org.apache.commons.lang.RandomStringUtils
7+
import org.gradle.testkit.runner.BuildResult
8+
import org.gradle.testkit.runner.BuildTask
9+
import org.gradle.testkit.runner.GradleRunner
10+
import org.gradle.testkit.runner.TaskOutcome
11+
import org.gradle.testkit.runner.internal.FeatureCheckBuildResult
12+
import org.junit.Assert.assertEquals
13+
import org.junit.Assert.assertTrue
14+
import org.junit.Before
15+
import org.junit.Rule
16+
import org.junit.Test
17+
import org.junit.rules.TemporaryFolder
18+
import java.io.File
19+
import java.io.FileReader
20+
21+
class OpenApiGradlePluginTest {
22+
@Rule
23+
@JvmField
24+
var testProjectDir: TemporaryFolder = TemporaryFolder()
25+
26+
private lateinit var projectTestDir: File
27+
private lateinit var buildFile: File
28+
private lateinit var projectBuildDir: File
29+
30+
private var baseBuildGradle = """plugins {
31+
id 'org.springframework.boot' version '2.2.0.RELEASE'
32+
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
33+
id 'java'
34+
id "com.github.johnrengelman.processes" version "0.5.0"
35+
id("org.springdoc.openapi-gradle-plugin")
36+
}
37+
38+
group = 'com.example'
39+
version = '0.0.1-SNAPSHOT'
40+
sourceCompatibility = '8'
41+
42+
repositories {
43+
mavenCentral()
44+
}
45+
46+
dependencies {
47+
implementation 'org.springframework.boot:spring-boot-starter-web'
48+
implementation group: 'org.springdoc', name: 'springdoc-openapi-webmvc-core', version: '1.4.0'
49+
}
50+
""".trimIndent()
51+
52+
@Before
53+
fun setup() {
54+
val acceptanceTestProject = File(this.javaClass.classLoader.getResource("acceptance-project")!!.path)
55+
projectTestDir = File(testProjectDir.newFolder(), "acceptence-project")
56+
57+
acceptanceTestProject.copyRecursively(projectTestDir)
58+
buildFile = File(projectTestDir, "build.gradle")
59+
60+
projectBuildDir = File(projectTestDir, "build")
61+
println("!!!!! $projectBuildDir !!!!!!!")
62+
}
63+
64+
@Test
65+
fun `default build no options`() {
66+
buildFile.writeText(baseBuildGradle)
67+
68+
val result = GradleRunner.create()
69+
.withProjectDir(projectTestDir)
70+
.withArguments("clean", "generateOpenApiDocs")
71+
.withPluginClasspath()
72+
.build()
73+
74+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
75+
76+
val openApiJsonFile = File(projectBuildDir, DEFAULT_OPEN_API_FILE_NAME)
77+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
78+
}
79+
80+
@Test
81+
fun `different output dir`() {
82+
var specialOutputDir = File(projectTestDir, "specialDir")
83+
specialOutputDir.mkdirs()
84+
85+
buildFile.writeText("""$baseBuildGradle
86+
openApi{
87+
outputDir = file("${specialOutputDir.path}")
88+
}
89+
""".trimMargin())
90+
91+
val result = GradleRunner.create()
92+
.withProjectDir(projectTestDir)
93+
.withArguments("clean", "generateOpenApiDocs")
94+
.withPluginClasspath()
95+
.build()
96+
97+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
98+
99+
val openApiJsonFile = File(specialOutputDir, DEFAULT_OPEN_API_FILE_NAME)
100+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
101+
}
102+
103+
@Test
104+
fun `different output file name`() {
105+
var specialOutputJsonFileName = RandomStringUtils.randomAlphanumeric(15)
106+
107+
buildFile.writeText("""$baseBuildGradle
108+
openApi{
109+
outputFileName = "$specialOutputJsonFileName"
110+
}
111+
""".trimMargin())
112+
113+
val result = GradleRunner.create()
114+
.withProjectDir(projectTestDir)
115+
.withArguments("clean", "generateOpenApiDocs")
116+
.withPluginClasspath()
117+
.build()
118+
119+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
120+
121+
val openApiJsonFile = File(projectBuildDir, specialOutputJsonFileName)
122+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
123+
}
124+
125+
@Test
126+
fun `using forked properties`() {
127+
buildFile.writeText("""$baseBuildGradle
128+
openApi{
129+
forkProperties = "-Dspring.profiles.active=multiple-endpoints"
130+
}
131+
""".trimMargin())
132+
133+
val result = GradleRunner.create()
134+
.withProjectDir(projectTestDir)
135+
.withArguments("clean", "generateOpenApiDocs")
136+
.withPluginClasspath()
137+
.build()
138+
139+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
140+
141+
val openApiJsonFile = File(projectBuildDir, DEFAULT_OPEN_API_FILE_NAME)
142+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 2)
143+
}
144+
145+
@Test
146+
fun `using forked properties via System properties`() {
147+
buildFile.writeText("""$baseBuildGradle
148+
openApi{
149+
forkProperties = System.properties
150+
}
151+
""".trimMargin())
152+
153+
val result = GradleRunner.create()
154+
.withProjectDir(projectTestDir)
155+
.withArguments("clean", "generateOpenApiDocs", "-Dspring.profiles.active=multiple-endpoints")
156+
.withPluginClasspath()
157+
.build()
158+
159+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
160+
161+
val openApiJsonFile = File(projectBuildDir, DEFAULT_OPEN_API_FILE_NAME)
162+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 2)
163+
}
164+
165+
@Test
166+
fun `configurable wait time`() {
167+
buildFile.writeText("""$baseBuildGradle
168+
openApi{
169+
forkProperties = "-Dspring.profiles.active=slower"
170+
waitTimeInSeconds = 60
171+
}
172+
""".trimMargin())
173+
174+
val result = GradleRunner.create()
175+
.withProjectDir(projectTestDir)
176+
.withArguments("clean", "generateOpenApiDocs")
177+
.withPluginClasspath()
178+
.build()
179+
180+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
181+
182+
val openApiJsonFile = File(projectBuildDir, DEFAULT_OPEN_API_FILE_NAME)
183+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
184+
}
185+
186+
@Test
187+
fun `using different api url`() {
188+
buildFile.writeText("""$baseBuildGradle
189+
openApi{
190+
apiDocsUrl = "http://localhost:8080/secret-api-docs"
191+
forkProperties = "-Dspring.profiles.active=different-url"
192+
}
193+
""".trimMargin())
194+
195+
val result = GradleRunner.create()
196+
.withProjectDir(projectTestDir)
197+
.withArguments("clean", "generateOpenApiDocs")
198+
.withPluginClasspath()
199+
.build()
200+
201+
assertEquals(TaskOutcome.SUCCESS, getTaskByName(result, "generateOpenApiDocs")?.outcome)
202+
203+
val openApiJsonFile = File(projectBuildDir, DEFAULT_OPEN_API_FILE_NAME)
204+
assertOpenApiJsonFileIsAsExpected(openApiJsonFile, 1)
205+
}
206+
207+
private fun assertOpenApiJsonFileIsAsExpected(openApiJsonFile: File, expectedNumberOfPaths: Int) {
208+
val openApiJson = getOpenApiJsonAtLocation(openApiJsonFile)
209+
assertEquals("3.0.1", openApiJson!!.string("openapi"))
210+
assertEquals(expectedNumberOfPaths, openApiJson.obj("paths")!!.size)
211+
}
212+
213+
private fun getOpenApiJsonAtLocation(path: File): JsonObject? {
214+
return Parser.default().parse(FileReader(path)) as JsonObject
215+
}
216+
217+
private fun getTaskByName(result: BuildResult, name: String): BuildTask? {
218+
return result.tasks.find { it.path.contains(name) }
219+
}
220+
}

Diff for: src/test/resources/acceptance-project/.gitignore

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
HELP.md
2+
.gradle
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
!**/src/main/**
6+
!**/src/test/**
7+
8+
### STS ###
9+
.apt_generated
10+
.classpath
11+
.factorypath
12+
.project
13+
.settings
14+
.springBeans
15+
.sts4-cache
16+
17+
### IntelliJ IDEA ###
18+
.idea
19+
*.iws
20+
*.iml
21+
*.ipr
22+
out/
23+
24+
### NetBeans ###
25+
/nbproject/private/
26+
/nbbuild/
27+
/dist/
28+
/nbdist/
29+
/.nb-gradle/
30+
31+
### VS Code ###
32+
.vscode/
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = 'demo'

0 commit comments

Comments
 (0)