diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml
new file mode 100644
index 000000000..08d50cc14
--- /dev/null
+++ b/.teamcity/pom.xml
@@ -0,0 +1,104 @@
+
+
+ 4.0.0
+ SpringPetclinic Config DSL Script
+ SpringPetclinic
+ SpringPetclinic_dsl
+ 1.0-SNAPSHOT
+
+
+ org.jetbrains.teamcity
+ configs-dsl-kotlin-parent
+ 1.0-SNAPSHOT
+
+
+
+
+ jetbrains-all
+ https://download.jetbrains.com/teamcity-repository
+
+ true
+
+
+
+ teamcity-server
+ https://teklada.teamcity.com/app/dsl-plugins-repository
+
+ true
+
+
+
+
+
+
+ JetBrains
+ https://download.jetbrains.com/teamcity-repository
+
+
+
+
+ ${basedir}
+
+
+ kotlin-maven-plugin
+ org.jetbrains.kotlin
+ ${kotlin.version}
+
+
+
+
+ compile
+ process-sources
+
+ compile
+
+
+
+ test-compile
+ process-test-sources
+
+ test-compile
+
+
+
+
+
+ org.jetbrains.teamcity
+ teamcity-configs-maven-plugin
+ ${teamcity.dsl.version}
+
+ kotlin
+ target/generated-configs
+
+
+
+
+
+
+
+ org.jetbrains.teamcity
+ configs-dsl-kotlin-latest
+ ${teamcity.dsl.version}
+ compile
+
+
+ org.jetbrains.teamcity
+ configs-dsl-kotlin-plugins-latest
+ 1.0-SNAPSHOT
+ pom
+ compile
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+ compile
+
+
+ org.jetbrains.kotlin
+ kotlin-script-runtime
+ ${kotlin.version}
+ compile
+
+
+
\ No newline at end of file
diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts
new file mode 100644
index 000000000..eb570db7a
--- /dev/null
+++ b/.teamcity/settings.kts
@@ -0,0 +1,177 @@
+import jetbrains.buildServer.configs.kotlin.*
+import jetbrains.buildServer.configs.kotlin.buildFeatures.PullRequests
+import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher
+import jetbrains.buildServer.configs.kotlin.buildFeatures.perfmon
+import jetbrains.buildServer.configs.kotlin.buildFeatures.pullRequests
+import jetbrains.buildServer.configs.kotlin.buildSteps.kotlinScript
+import jetbrains.buildServer.configs.kotlin.buildSteps.maven
+import jetbrains.buildServer.configs.kotlin.triggers.vcs
+
+/*
+The settings script is an entry point for defining a TeamCity
+project hierarchy. The script should contain a single call to the
+project() function with a Project instance or an init function as
+an argument.
+
+VcsRoots, BuildTypes, Templates, and subprojects can be
+registered inside the project using the vcsRoot(), buildType(),
+template(), and subProject() methods respectively.
+
+To debug settings scripts in command-line, run the
+
+ mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate
+
+command and attach your debugger to the port 8000.
+
+To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View
+-> Tool Windows -> Maven Projects), find the generate task node
+(Plugins -> teamcity-configs -> teamcity-configs:generate), the
+'Debug' option is available in the context menu for the task.
+*/
+
+version = "2024.03"
+
+project {
+
+ buildType(Build)
+ buildType(Deploy)
+
+ params {
+ password("github_key", "credentialsJSON:405d3744-add6-49d5-a271-6dfd185492c2")
+ }
+}
+
+object Build : BuildType({
+ name = "Build"
+
+ artifactRules = "+:target/*.jar"
+ publishArtifacts = PublishMode.SUCCESSFUL
+
+ vcs {
+ root(DslContext.settingsRoot)
+ }
+
+ steps {
+ maven {
+ name = "build"
+ id = "build"
+ goals = "package"
+ }
+ }
+
+ triggers {
+ vcs {
+ }
+ }
+
+ features {
+ perfmon {
+ }
+ pullRequests {
+ provider = github {
+ authType = vcsRoot()
+ filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER_OR_COLLABORATOR
+ }
+ }
+ commitStatusPublisher {
+ publisher = github {
+ githubUrl = "https://api.github.com"
+ authType = vcsRoot()
+ }
+ }
+ }
+})
+
+object Deploy : BuildType({
+ name = "Deploy"
+
+ steps {
+ kotlinScript {
+ id = "kotlinScript"
+ content = """
+ @file:DependsOn("com.squareup.okhttp3:okhttp:4.11.0")
+ @file:DependsOn("org.json:json:20211205")
+
+ import okhttp3.*
+ import okhttp3.MediaType.Companion.toMediaType
+ import org.json.JSONObject
+ import java.io.File
+ import java.io.IOException
+
+
+
+ val client = OkHttpClient()
+
+ fun createRelease(client: OkHttpClient, token: String, owner: String, repo: String, tagName: String, name: String, body: String): Int {
+ val url = "https://api.github.com/repos/${'$'}owner/${'$'}repo/releases"
+ val json = JSONObject()
+ .put("tag_name", tagName)
+ .put("name", name)
+ .put("body", body)
+
+ val requestBody = RequestBody.create("application/json; charset=utf-8".toMediaType(), json.toString())
+ val request = Request.Builder()
+ .url(url)
+ .header("Authorization", "token ${'$'}token")
+ .post(requestBody)
+ .build()
+
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code ${'$'}response")
+ val responseData = response.body?.string()
+ val jsonResponse = JSONObject(responseData)
+ return jsonResponse.getInt("id")
+ }
+ }
+
+ fun uploadFileToRelease(client: OkHttpClient, token: String, owner: String, repo: String, releaseId: Int, filePath: String) {
+ val url = "https://uploads.github.com/repos/${'$'}owner/${'$'}repo/releases/${'$'}releaseId/assets?name=${'$'}{File(filePath).name}"
+ val file = File(filePath)
+ val requestBody = RequestBody.create(("application/zip").toMediaType(), file)
+
+ val request = Request.Builder()
+ .url(url)
+ .header("Authorization", "token ${'$'}token")
+ .header("Content-Type", "application/zip")
+ .post(requestBody)
+ .build()
+
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code ${'$'}response")
+ println("File uploaded successfully")
+ }
+ }
+
+
+ fun getArg(args: Array, argName: String): String {
+ return if (args.contains("-${'$'}argName")) args[1 + args.indexOf("-${'$'}argName")]
+ else ""
+ }
+
+ val repoOwner = getArg(args,"repoOwner" )
+ val repoName = getArg(args,"repoName" )
+ val tagName = getArg(args,"tagName" )
+ val releaseName = getArg(args,"releaseName" )
+ val releaseDescription = getArg(args,"description" )
+ val filePath = getArg(args,"filePath" )
+ val githubToken = getArg(args,"token" )
+
+ val releaseId = createRelease(client, githubToken, repoOwner, repoName, tagName, releaseName, releaseDescription)
+ uploadFileToRelease(client, githubToken, repoOwner, repoName, releaseId, filePath)
+ """.trimIndent()
+ arguments = """-repoOwner "TPG-Teklada" -repoName "spring-petclinic" -tagName "v1.0.0" -releaseName "Release 1.0.0" -description "Description of the release" -filePath "*.jar" -token "%github_key%""""
+ }
+ }
+
+ dependencies {
+ dependency(Build) {
+ snapshot {
+ onDependencyFailure = FailureAction.FAIL_TO_START
+ }
+
+ artifacts {
+ artifactRules = "+:*.jar"
+ }
+ }
+ }
+})
diff --git a/github_release.main.kts b/github_release.main.kts
new file mode 100644
index 000000000..72ffa1a5e
--- /dev/null
+++ b/github_release.main.kts
@@ -0,0 +1,95 @@
+// I know this is crap. I have never written Kotlin before and just wanted something that did the job for the sake of the exercise.
+// This is not how I normally code, I promise.
+
+@file:DependsOn("com.squareup.okhttp3:okhttp:4.11.0")
+@file:DependsOn("org.json:json:20211205")
+
+import okhttp3.*
+import okhttp3.MediaType.Companion.toMediaType
+import org.json.JSONObject
+import java.io.File
+import java.io.IOException
+
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.PathMatcher
+import java.nio.file.FileSystems
+
+
+val client = OkHttpClient()
+
+fun createRelease(client: OkHttpClient, token: String, owner: String, repo: String, tagName: String, name: String, body: String): Int {
+ val url = "https://api.github.com/repos/$owner/$repo/releases"
+ val json = JSONObject()
+ .put("tag_name", tagName)
+ .put("name", name)
+ .put("body", body)
+
+ val requestBody = RequestBody.create("application/json; charset=utf-8".toMediaType(), json.toString())
+ val request = Request.Builder()
+ .url(url)
+ .header("Authorization", "token $token")
+ .post(requestBody)
+ .build()
+
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code $response")
+ val responseData = response.body?.string()
+ val jsonResponse = JSONObject(responseData)
+ return jsonResponse.getInt("id")
+ }
+}
+
+fun uploadFileToRelease(client: OkHttpClient, token: String, owner: String, repo: String, releaseId: Int, filePath: String) {
+ val url = "https://uploads.github.com/repos/$owner/$repo/releases/$releaseId/assets?name=${File(filePath).name}"
+ val file = File(filePath)
+ val requestBody = RequestBody.create(("application/zip").toMediaType(), file)
+
+ val request = Request.Builder()
+ .url(url)
+ .header("Authorization", "token $token")
+ .header("Content-Type", "application/zip")
+ .post(requestBody)
+ .build()
+
+ client.newCall(request).execute().use { response ->
+ if (!response.isSuccessful) throw IOException("Unexpected code $response")
+ println("File uploaded successfully")
+ }
+}
+
+
+fun getArg(args: Array, argName: String): String {
+ return if (args.contains("-$argName")) args[1 + args.indexOf("-$argName")]
+ else ""
+}
+
+fun findFirstFileMatchingPattern(pattern: String): String? {
+ val matcher: PathMatcher = FileSystems.getDefault().getPathMatcher("glob:$pattern")
+ val dir = Paths.get("").toAbsolutePath()
+ Files.walk(dir).use { stream ->
+ return stream
+ .filter { matcher.matches(it.fileName) }
+ .findFirst()
+ .map { it.toAbsolutePath().toString() }
+ .orElse(null)
+ }
+}
+
+
+val repoOwner = getArg(args,"repoOwner" )
+val repoName = getArg(args,"repoName" )
+val tagName = getArg(args,"tagName" )
+val releaseName = getArg(args,"releaseName" )
+val releaseDescription = getArg(args,"description" )
+val filePath = getArg(args,"filePath" )
+val githubToken = getArg(args,"token" )
+
+val realFilePath = findFirstFileMatchingPattern(filePath)
+if (realFilePath != null) {
+ val releaseId = createRelease(client, githubToken, repoOwner, repoName, tagName, releaseName, releaseDescription)
+ uploadFileToRelease(client, githubToken, repoOwner, repoName, releaseId, realFilePath!!)
+}
+else{
+ throw IOException("No file found")
+}
\ No newline at end of file