mirror of
https://github.com/dalbodeule/sh0rt.kr-pdns.git
synced 2026-11-05 22:29:34 +09:00
Compare commits
2
Commits
5936622f64
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
029d1275b9 | ||
|
|
47461c505f |
No files matched your search
+44
-5
@@ -1,5 +1,5 @@
|
|||||||
# Created by https://www.toptal.com/developers/gitignore/api/gradle,java,kotlin,macos,intellij
|
# Created by https://www.toptal.com/developers/gitignore/api/gradle,java,kotlin,macos,linux,windows,intellij
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,java,kotlin,macos,intellij
|
# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,java,kotlin,macos,linux,windows,intellij
|
||||||
|
|
||||||
### Intellij ###
|
### Intellij ###
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
@@ -153,6 +153,21 @@ replay_pid*
|
|||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
### macOS ###
|
### macOS ###
|
||||||
# General
|
# General
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@@ -186,6 +201,32 @@ Temporary Items
|
|||||||
# iCloud generated files
|
# iCloud generated files
|
||||||
*.icloud
|
*.icloud
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
### Gradle ###
|
### Gradle ###
|
||||||
.gradle
|
.gradle
|
||||||
**/build/
|
**/build/
|
||||||
@@ -213,6 +254,4 @@ gradle-app.setting
|
|||||||
# Java heap dump
|
# Java heap dump
|
||||||
*.hprof
|
*.hprof
|
||||||
|
|
||||||
.env
|
# End of https://www.toptal.com/developers/gitignore/api/gradle,java,kotlin,macos,linux,windows,intellij
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/gradle,java,kotlin,macos,intellij
|
|
||||||
Generated
+9
@@ -3,4 +3,13 @@
|
|||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="21" />
|
<bytecodeTargetLevel target="21" />
|
||||||
</component>
|
</component>
|
||||||
|
<component name="JavacSettings">
|
||||||
|
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||||
|
<module name="dnsapi" options="-parameters" />
|
||||||
|
<module name="dnsapi.aot" options="-parameters" />
|
||||||
|
<module name="dnsapi.aotTest" options="-parameters" />
|
||||||
|
<module name="dnsapi.test" options="-parameters" />
|
||||||
|
<module name="space.mori.dnsapi.main" options="-parameters" />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="db-forest-configuration">
|
||||||
|
<data version="2">.
|
||||||
|
----------------------------------------
|
||||||
|
1:0:97ab7754-c9e9-4e6f-b775-574d260344a3
|
||||||
|
2:0:1b4583fc-5364-493b-be71-9d4c536f6902
|
||||||
|
.</data>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.9.24" />
|
<option name="externalSystemId" value="Gradle" />
|
||||||
|
<option name="version" value="2.4.21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SwiftToolchain">
|
||||||
|
<option name="toolchain" value="xcode-/Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+70
-34
@@ -1,26 +1,34 @@
|
|||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
|
import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("java")
|
id("java")
|
||||||
id("org.springframework.boot") version "3.3.0"
|
id("org.springframework.boot") version "4.1.2"
|
||||||
id("io.spring.dependency-management") version "1.1.5"
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
id("org.hibernate.orm") version "6.5.2.Final"
|
id("org.hibernate.orm") version "7.1.4.Final"
|
||||||
id("org.graalvm.buildtools.native") version "0.10.2"
|
id("org.graalvm.buildtools.native") version "1.1.17"
|
||||||
kotlin("jvm") version "1.9.24"
|
kotlin("jvm") version "2.4.21"
|
||||||
kotlin("plugin.spring") version "1.9.24"
|
kotlin("plugin.spring") version "2.4.21"
|
||||||
kotlin("plugin.jpa") version "1.9.24"
|
kotlin("plugin.jpa") version "2.4.21"
|
||||||
}
|
}
|
||||||
|
|
||||||
val datetimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm")
|
val datetimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm")
|
||||||
|
val jvmVersion = 25
|
||||||
|
|
||||||
group = "space.mori"
|
group = "space.mori"
|
||||||
version = "0.0.1"
|
version = "1.0.0"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
toolchain.languageVersion.set(JavaLanguageVersion.of(jvmVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
freeCompilerArgs.add("-Xjsr305=strict")
|
||||||
|
jvmTarget.set(JvmTarget.fromTarget(jvmVersion.toString()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -31,66 +39,94 @@ dependencies {
|
|||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-validation")
|
implementation("org.springframework.boot:spring-boot-starter-validation")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-webmvc")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
implementation("tools.jackson.module:jackson-module-kotlin")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2")
|
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:3.2.0")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-bindings
|
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-bindings
|
||||||
implementation("org.springframework.cloud:spring-cloud-bindings:2.0.3")
|
implementation("org.springframework.cloud:spring-cloud-bindings:2.0.4")
|
||||||
|
|
||||||
implementation("com.google.code.gson:gson:2.11.0")
|
implementation("com.google.code.gson:gson:2.15.0")
|
||||||
implementation("io.github.cdimascio:dotenv-kotlin:6.4.1")
|
implementation("io.github.cdimascio:dotenv-kotlin:6.5.1")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
implementation("com.squareup.okhttp3:okhttp:5.5.1")
|
||||||
|
|
||||||
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
|
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
|
||||||
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
testImplementation("org.springframework.security:spring-security-test")
|
testImplementation("org.springframework.security:spring-security-test")
|
||||||
|
testImplementation("com.squareup.okhttp3:mockwebserver3:5.5.1")
|
||||||
|
testRuntimeOnly("com.h2database:h2")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
|
||||||
kotlinOptions {
|
|
||||||
freeCompilerArgs += "-Xjsr305=strict"
|
|
||||||
jvmTarget = "21"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named<Test>("test") {
|
||||||
|
useJUnitPlatform { excludeTags("openapi-generation") }
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Test>("generateOpenApi") {
|
||||||
|
group = "documentation"
|
||||||
|
description = "Generate the root openapi.json from the running Spring application"
|
||||||
|
dependsOn(tasks.testClasses)
|
||||||
|
testClassesDirs = sourceSets.test.get().output.classesDirs
|
||||||
|
classpath = sourceSets.test.get().runtimeClasspath
|
||||||
|
useJUnitPlatform { includeTags("openapi-generation") }
|
||||||
|
val openApiFile = layout.projectDirectory.file("openapi.json")
|
||||||
|
outputs.file(openApiFile)
|
||||||
|
systemProperty("openapi.output", openApiFile.asFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<Exec>("updateReadme") {
|
||||||
|
group = "documentation"
|
||||||
|
description = "Update README.md from the generated OpenAPI document"
|
||||||
|
dependsOn("generateOpenApi")
|
||||||
|
commandLine("python3", "scripts/update_readme.py")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("updateDocs") {
|
||||||
|
group = "documentation"
|
||||||
|
description = "Refresh openapi.json and README.md"
|
||||||
|
dependsOn("updateReadme")
|
||||||
|
}
|
||||||
|
|
||||||
tasks.withType<BootBuildImage> {
|
tasks.withType<BootBuildImage> {
|
||||||
val dockerId = System.getenv("DOCKER_USERNAME") ?: "git.mori.space/dalbodeule"
|
val dockerId = System.getenv("DOCKER_USERNAME") ?: "dalbodeule"
|
||||||
val dockerPw = System.getenv("DOCKER_PASSWORD") ?: ""
|
val dockerPw = System.getenv("DOCKER_PASSWORD")
|
||||||
|
|
||||||
docker {
|
docker {
|
||||||
publish = true
|
publish = true
|
||||||
publishRegistry {
|
if (!dockerPw.isNullOrBlank()) {
|
||||||
username = dockerId
|
publishRegistry {
|
||||||
password = dockerPw
|
username = dockerId
|
||||||
|
password = dockerPw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageName.set("$dockerId/dnsapi")
|
imageName.set("$dockerId/dnsapi")
|
||||||
tags.set(setOf("$dockerId/dnsapi:latest", "$dockerId/dnsapi:${datetimeFormatter.format(LocalDateTime.now())}"))
|
if (providers.gradleProperty("multiArchImage").orNull == "true") {
|
||||||
|
tags.set(emptySet<String>())
|
||||||
|
} else {
|
||||||
|
tags.set(setOf("$dockerId/dnsapi:latest", "$dockerId/dnsapi:${datetimeFormatter.format(LocalDateTime.now())}"))
|
||||||
|
}
|
||||||
buildpacks.set(setOf("docker.io/paketobuildpacks/oracle", "urn:cnb:builder:paketo-buildpacks/java-native-image"))
|
buildpacks.set(setOf("docker.io/paketobuildpacks/oracle", "urn:cnb:builder:paketo-buildpacks/java-native-image"))
|
||||||
|
|
||||||
environment = mapOf(
|
environment = mapOf(
|
||||||
"BP_NATIVE_IMAGE" to "true",
|
"BP_NATIVE_IMAGE" to "true",
|
||||||
"BP_NATIVE_IMAGE_BUILD_ARGUMENTS" to "-H:+UnlockExperimentalVMOptions",
|
"BP_NATIVE_IMAGE_BUILD_ARGUMENTS" to "-H:+UnlockExperimentalVMOptions",
|
||||||
"BP_JVM_TYPE" to "JDK",
|
"BP_JVM_TYPE" to "JDK",
|
||||||
"BP_JVM_VERSION" to "21",
|
"BP_JVM_VERSION" to jvmVersion.toString(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
hibernate {
|
hibernate {
|
||||||
enhancement {
|
enhancement {}
|
||||||
enableAssociationManagement.set(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named<org.springframework.boot.gradle.tasks.run.BootRun>("bootRun") {
|
tasks.named<org.springframework.boot.gradle.tasks.run.BootRun>("bootRun") {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.2-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
+290
-321
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.0.1",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Cloudflare compatible PowerDNS API Server",
|
"title": "Cloudflare compatible PowerDNS API Server",
|
||||||
"version": "v1.0.0"
|
"version": "v1.0.0"
|
||||||
@@ -16,6 +16,159 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/zones/{zone_id}/dns_records/{dns_record_id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"record"
|
||||||
|
],
|
||||||
|
"summary": "Get Record by ID",
|
||||||
|
"operationId": "getRecordByCfid",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "zone_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns_record_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Return Record",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTORecordResponseDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"record"
|
||||||
|
],
|
||||||
|
"summary": "Update Record by ID",
|
||||||
|
"operationId": "updateRecord",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "zone_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns_record_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/RecordRequestDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Return Record",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTORecordResponseDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"record"
|
||||||
|
],
|
||||||
|
"summary": "Remove Record by ID",
|
||||||
|
"operationId": "deleteRecord",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "zone_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns_record_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Return Record",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTODeleteResponseWithId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ApiResponseDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/zones": {
|
"/zones": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -176,159 +329,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/zones/{zone_id}/dns_records/{dns_record_id}": {
|
|
||||||
"get": {
|
|
||||||
"tags": [
|
|
||||||
"record"
|
|
||||||
],
|
|
||||||
"summary": "Get Record by ID",
|
|
||||||
"operationId": "getRecordByCfid",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "zone_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dns_record_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Return Record",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTORecordResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"tags": [
|
|
||||||
"record"
|
|
||||||
],
|
|
||||||
"summary": "Remove Record by ID",
|
|
||||||
"operationId": "deleteRecord",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "zone_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dns_record_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Return Record",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTODeleteResponseWithId"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"tags": [
|
|
||||||
"record"
|
|
||||||
],
|
|
||||||
"summary": "Update Record by ID",
|
|
||||||
"operationId": "updateRecord",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "zone_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dns_record_id",
|
|
||||||
"in": "path",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/RecordRequestDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Return Record",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTORecordResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ApiResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/zones/{cfid}": {
|
"/zones/{cfid}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -414,122 +414,9 @@
|
|||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {
|
||||||
"DomainRequestDTO": {
|
|
||||||
"required": [
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Domain name(TLD)",
|
|
||||||
"example": "example.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "Request DTO for Domain"
|
|
||||||
},
|
|
||||||
"ApiResponseDTO": {
|
|
||||||
"required": [
|
|
||||||
"errors",
|
|
||||||
"messages",
|
|
||||||
"success"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"success": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ErrorOrMessage"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ErrorOrMessage"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ErrorOrMessage": {
|
|
||||||
"required": [
|
|
||||||
"code",
|
|
||||||
"message"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"code": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32"
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ApiResponseDTODomainResponseDTO": {
|
|
||||||
"required": [
|
|
||||||
"errors",
|
|
||||||
"messages",
|
|
||||||
"success"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"success": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"errors": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ErrorOrMessage"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"messages": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/ErrorOrMessage"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"result": {
|
|
||||||
"$ref": "#/components/schemas/DomainResponseDTO"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"DomainResponseDTO": {
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Domain CFID",
|
|
||||||
"example": "123e4567e89b12d3a456426655440000"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Domain name(TLD)",
|
|
||||||
"example": "example.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "Response DTO for Domain"
|
|
||||||
},
|
|
||||||
"RecordRequestDTO": {
|
"RecordRequestDTO": {
|
||||||
"required": [
|
|
||||||
"comment",
|
|
||||||
"content",
|
|
||||||
"name",
|
|
||||||
"proxied",
|
|
||||||
"ttl",
|
|
||||||
"type"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"description": "Request DTO for Record",
|
||||||
"properties": {
|
"properties": {
|
||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -541,21 +428,24 @@
|
|||||||
"description": "Host name",
|
"description": "Host name",
|
||||||
"example": "www"
|
"example": "www"
|
||||||
},
|
},
|
||||||
"content": {
|
"value": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Record data",
|
"description": "Record data",
|
||||||
"example": "192.0.2.1"
|
"example": "192.0.2.1"
|
||||||
},
|
},
|
||||||
"ttl": {
|
"ttl": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "TTL (Time to Live)",
|
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
"description": "TTL (Time to Live)",
|
||||||
"example": 3600
|
"example": 3600
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"type": "integer",
|
"type": [
|
||||||
"description": "Priority",
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
"description": "Priority",
|
||||||
"example": 0
|
"example": 0
|
||||||
},
|
},
|
||||||
"proxied": {
|
"proxied": {
|
||||||
@@ -567,15 +457,42 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "comment"
|
"description": "comment"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"description": "Request DTO for Record"
|
},
|
||||||
|
"ApiResponseDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ErrorOrMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ErrorOrMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ErrorOrMessage": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ApiResponseDTORecordResponseDTO": {
|
"ApiResponseDTORecordResponseDTO": {
|
||||||
"required": [
|
|
||||||
"errors",
|
|
||||||
"messages",
|
|
||||||
"success"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -594,26 +511,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/components/schemas/RecordResponseDTO"
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/RecordResponseDTO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RecordResponseDTO": {
|
"RecordResponseDTO": {
|
||||||
"required": [
|
|
||||||
"content",
|
|
||||||
"createdOn",
|
|
||||||
"id",
|
|
||||||
"locked",
|
|
||||||
"modifiedOn",
|
|
||||||
"name",
|
|
||||||
"proxiable",
|
|
||||||
"proxied",
|
|
||||||
"ttl",
|
|
||||||
"type",
|
|
||||||
"zoneId",
|
|
||||||
"zoneName"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"description": "Response DTO for Record",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -656,9 +567,12 @@
|
|||||||
"example": "2014-01-01T05:20:00.12345Z"
|
"example": "2014-01-01T05:20:00.12345Z"
|
||||||
},
|
},
|
||||||
"priority": {
|
"priority": {
|
||||||
"type": "integer",
|
"type": [
|
||||||
"description": "Record priority",
|
"integer",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
"description": "Record priority",
|
||||||
"example": 0
|
"example": 0
|
||||||
},
|
},
|
||||||
"proxiable": {
|
"proxiable": {
|
||||||
@@ -673,8 +587,8 @@
|
|||||||
},
|
},
|
||||||
"ttl": {
|
"ttl": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Record TTL",
|
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
"description": "Record TTL",
|
||||||
"example": 300
|
"example": 300
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -683,18 +597,26 @@
|
|||||||
"example": false
|
"example": false
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
"type": "string",
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"description": "Record comments"
|
"description": "Record comments"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"description": "Response DTO for Record"
|
|
||||||
},
|
},
|
||||||
"ApiResponseDTOListDomainResponseDTO": {
|
"DomainRequestDTO": {
|
||||||
"required": [
|
"type": "object",
|
||||||
"errors",
|
"description": "Request DTO for Domain",
|
||||||
"messages",
|
"properties": {
|
||||||
"success"
|
"name": {
|
||||||
],
|
"type": "string",
|
||||||
|
"description": "Domain name(TLD)",
|
||||||
|
"example": "example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ApiResponseDTODomainResponseDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -713,7 +635,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/DomainResponseDTO"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DomainResponseDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Response DTO for Domain",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Domain CFID",
|
||||||
|
"example": "123e4567e89b12d3a456426655440000"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Domain name(TLD)",
|
||||||
|
"example": "example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ApiResponseDTOListDomainResponseDTO": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ErrorOrMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ErrorOrMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/DomainResponseDTO"
|
"$ref": "#/components/schemas/DomainResponseDTO"
|
||||||
}
|
}
|
||||||
@@ -721,11 +692,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ApiResponseDTOListRecordResponseDTO": {
|
"ApiResponseDTOListRecordResponseDTO": {
|
||||||
"required": [
|
|
||||||
"errors",
|
|
||||||
"messages",
|
|
||||||
"success"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -744,7 +710,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"type": "array",
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/RecordResponseDTO"
|
"$ref": "#/components/schemas/RecordResponseDTO"
|
||||||
}
|
}
|
||||||
@@ -752,11 +721,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ApiResponseDTODeleteResponseWithId": {
|
"ApiResponseDTODeleteResponseWithId": {
|
||||||
"required": [
|
|
||||||
"errors",
|
|
||||||
"messages",
|
|
||||||
"success"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"success": {
|
"success": {
|
||||||
@@ -775,14 +739,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"$ref": "#/components/schemas/DeleteResponseWithId"
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/DeleteResponseWithId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DeleteResponseWithId": {
|
"DeleteResponseWithId": {
|
||||||
"required": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -794,6 +762,7 @@
|
|||||||
"securitySchemes": {
|
"securitySchemes": {
|
||||||
"api token": {
|
"api token": {
|
||||||
"type": "http",
|
"type": "http",
|
||||||
|
"name": "api token",
|
||||||
"scheme": "bearer"
|
"scheme": "bearer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 <image-tag>" >&2
|
||||||
|
echo "Set IMAGE_REPOSITORY to override the default repository." >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
tag=$1
|
||||||
|
if [[ ! $tag =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,121}$ ]]; then
|
||||||
|
echo "Invalid Docker image tag: $tag" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
repository=${IMAGE_REPOSITORY:-${DOCKER_USERNAME:-dalbodeule}/dnsapi}
|
||||||
|
publish_latest=${PUBLISH_LATEST:-1}
|
||||||
|
dry_run=${DRY_RUN:-0}
|
||||||
|
resume=${RESUME:-0}
|
||||||
|
root_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
|
||||||
|
if [[ $publish_latest != 0 && $publish_latest != 1 ]]; then
|
||||||
|
echo "PUBLISH_LATEST must be 0 or 1" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ $dry_run != 0 && $dry_run != 1 ]]; then
|
||||||
|
echo "DRY_RUN must be 0 or 1" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ $resume != 0 && $resume != 1 ]]; then
|
||||||
|
echo "RESUME must be 0 or 1" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [[ $dry_run == 0 ]]; then
|
||||||
|
command -v docker >/dev/null || { echo "Docker is required" >&2; exit 1; }
|
||||||
|
docker buildx version >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if [[ $dry_run == 1 ]]; then
|
||||||
|
printf 'DRY RUN:'
|
||||||
|
printf ' %q' "$@"
|
||||||
|
printf '\n'
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "$root_dir"
|
||||||
|
for arch in amd64 arm64; do
|
||||||
|
image="$repository:$tag-$arch"
|
||||||
|
if [[ $resume == 1 && $dry_run == 0 ]] && docker image inspect "$image" >/dev/null 2>&1; then
|
||||||
|
actual_platform=$(docker image inspect "$image" --format '{{.Os}}/{{.Architecture}}')
|
||||||
|
if [[ $actual_platform != "linux/$arch" ]]; then
|
||||||
|
echo "$image has platform $actual_platform, expected linux/$arch" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Reusing local image $image"
|
||||||
|
else
|
||||||
|
run ./gradlew bootBuildImage \
|
||||||
|
-PmultiArchImage=true \
|
||||||
|
"--imagePlatform=linux/$arch" \
|
||||||
|
"--imageName=$image" \
|
||||||
|
--no-publishImage
|
||||||
|
fi
|
||||||
|
run docker push "$image"
|
||||||
|
done
|
||||||
|
|
||||||
|
manifest_args=(docker buildx imagetools create --tag "$repository:$tag")
|
||||||
|
if [[ $publish_latest == 1 ]]; then
|
||||||
|
manifest_args+=(--tag "$repository:latest")
|
||||||
|
fi
|
||||||
|
manifest_args+=("$repository:$tag-amd64" "$repository:$tag-arm64")
|
||||||
|
run "${manifest_args[@]}"
|
||||||
|
run docker buildx imagetools inspect "$repository:$tag"
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Refresh the generated README section from the root OpenAPI document."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
START = "<!-- BEGIN GENERATED API -->"
|
||||||
|
END = "<!-- END GENERATED API -->"
|
||||||
|
METHODS = ("get", "post", "put", "patch", "delete")
|
||||||
|
|
||||||
|
|
||||||
|
def schema_type(schema):
|
||||||
|
if not schema:
|
||||||
|
return "—"
|
||||||
|
if "$ref" in schema:
|
||||||
|
name = schema["$ref"].rsplit("/", 1)[-1]
|
||||||
|
return f"[{name}](#{name.lower()})"
|
||||||
|
if "oneOf" in schema:
|
||||||
|
return " or ".join(schema_type(part) for part in schema["oneOf"])
|
||||||
|
kind = schema.get("type", "object")
|
||||||
|
if isinstance(kind, list):
|
||||||
|
return " or ".join(
|
||||||
|
f"array of {schema_type(schema.get('items'))}" if part == "array" else part
|
||||||
|
for part in kind
|
||||||
|
)
|
||||||
|
if kind == "array":
|
||||||
|
return f"array of {schema_type(schema.get('items'))}"
|
||||||
|
return kind
|
||||||
|
|
||||||
|
|
||||||
|
def media_schema(content):
|
||||||
|
return schema_type(content.get("application/json", {}).get("schema"))
|
||||||
|
|
||||||
|
|
||||||
|
def cell(value):
|
||||||
|
return str(value or "—").replace("|", "\\|").replace("\n", " ")
|
||||||
|
|
||||||
|
|
||||||
|
def generate(spec):
|
||||||
|
lines = [
|
||||||
|
"Generated from [openapi.json](openapi.json). Do not edit this section by hand.",
|
||||||
|
"",
|
||||||
|
"### Endpoints",
|
||||||
|
"",
|
||||||
|
"| Method | Path | Summary | Request body | Success response |",
|
||||||
|
"| --- | --- | --- | --- | --- |",
|
||||||
|
]
|
||||||
|
|
||||||
|
for path, operations in sorted(spec["paths"].items()):
|
||||||
|
for method in METHODS:
|
||||||
|
operation = operations.get(method)
|
||||||
|
if not operation:
|
||||||
|
continue
|
||||||
|
request = media_schema(operation.get("requestBody", {}).get("content", {}))
|
||||||
|
responses = operation.get("responses", {})
|
||||||
|
success_code = next((code for code in sorted(responses) if code.startswith("2")), None)
|
||||||
|
response = responses.get(success_code, {})
|
||||||
|
result = media_schema(response.get("content", {}))
|
||||||
|
lines.append(
|
||||||
|
f"| `{method.upper()}` | `{path}` | {cell(operation.get('summary'))} | "
|
||||||
|
f"{cell(request)} | {cell(success_code)}: {cell(result)} |"
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.extend(["", "### Schemas", ""])
|
||||||
|
for name, schema in sorted(spec.get("components", {}).get("schemas", {}).items()):
|
||||||
|
lines.extend([f"#### {name}", ""])
|
||||||
|
if schema.get("description"):
|
||||||
|
lines.extend([schema["description"], ""])
|
||||||
|
properties = schema.get("properties", {})
|
||||||
|
if not properties:
|
||||||
|
lines.extend([f"Type: {schema_type(schema)}", ""])
|
||||||
|
continue
|
||||||
|
required = set(schema.get("required", []))
|
||||||
|
lines.extend(["| Field | Type | Required | Description |", "| --- | --- | --- | --- |"])
|
||||||
|
for field, definition in properties.items():
|
||||||
|
lines.append(
|
||||||
|
f"| `{field}` | {cell(schema_type(definition))} | "
|
||||||
|
f"{'Yes' if field in required else 'No'} | {cell(definition.get('description'))} |"
|
||||||
|
)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
spec = json.loads((ROOT / "openapi.json").read_text(encoding="utf-8"))
|
||||||
|
readme_path = ROOT / "README.md"
|
||||||
|
readme = readme_path.read_text(encoding="utf-8")
|
||||||
|
if readme.count(START) != 1 or readme.count(END) != 1:
|
||||||
|
raise ValueError("README.md must contain one pair of generated API markers")
|
||||||
|
before, remainder = readme.split(START, 1)
|
||||||
|
_, after = remainder.split(END, 1)
|
||||||
|
readme_path.write_text(before + START + "\n" + generate(spec) + END + after, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -6,10 +6,10 @@ import okhttp3.MediaType.Companion.toMediaType
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -21,40 +21,46 @@ class PowerDNSAPIClient() {
|
|||||||
private lateinit var apiKey: String
|
private lateinit var apiKey: String
|
||||||
|
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private val client = OkHttpClient()
|
private val client = OkHttpClient.Builder()
|
||||||
|
.callTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
@Throws(PowerDNSAPIException::class)
|
private fun execute(request: Request) {
|
||||||
fun createZone(zoneName: String): Response {
|
client.newCall(request).execute().use { response ->
|
||||||
try {
|
if (response.isSuccessful) return
|
||||||
val body = gson.toJson(
|
|
||||||
mapOf(
|
|
||||||
"name" to "$zoneName.",
|
|
||||||
"kind" to "Primary"
|
|
||||||
)
|
|
||||||
).toRequestBody()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("$apiUrl/api/v1/servers/localhost/zones")
|
|
||||||
.addHeader("X-API-Key", apiKey)
|
|
||||||
.addHeader("Accept", "application/json")
|
|
||||||
.addHeader("Content-Type", "application/json")
|
|
||||||
.post(body)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
val error = runCatching {
|
||||||
if (!response.isSuccessful) {
|
gson.fromJson(response.peekBody(64 * 1024L).string(), PowerDNSAPIError::class.java)
|
||||||
val str = response.body?.string()
|
}.getOrNull()
|
||||||
val error = gson.fromJson(str, PowerDNSAPIError::class.java)
|
throw PowerDNSAPIException(
|
||||||
throw PowerDNSAPIException(error)
|
if (error?.error.isNullOrBlank())
|
||||||
}
|
PowerDNSAPIError("PowerDNS API returned HTTP ${response.code}", null)
|
||||||
return response
|
else error
|
||||||
} catch(ex: Exception) {
|
)
|
||||||
println(ex)
|
|
||||||
throw ex
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PowerDNSAPIException::class)
|
@Throws(PowerDNSAPIException::class)
|
||||||
fun deleteZone(zoneName: String): Response {
|
fun createZone(zoneName: String) {
|
||||||
|
val body = gson.toJson(
|
||||||
|
mapOf(
|
||||||
|
"name" to "$zoneName.",
|
||||||
|
"kind" to "Primary"
|
||||||
|
)
|
||||||
|
).toRequestBody()
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("$apiUrl/api/v1/servers/localhost/zones")
|
||||||
|
.addHeader("X-API-Key", apiKey)
|
||||||
|
.addHeader("Accept", "application/json")
|
||||||
|
.addHeader("Content-Type", "application/json")
|
||||||
|
.post(body)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
execute(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(PowerDNSAPIException::class)
|
||||||
|
fun deleteZone(zoneName: String) {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName.")
|
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName.")
|
||||||
.addHeader("X-API-Key", apiKey)
|
.addHeader("X-API-Key", apiKey)
|
||||||
@@ -63,17 +69,11 @@ class PowerDNSAPIClient() {
|
|||||||
.delete()
|
.delete()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
execute(request)
|
||||||
if(!response.isSuccessful) {
|
|
||||||
val str = response.body?.string()
|
|
||||||
val error = gson.fromJson(str, PowerDNSAPIError::class.java)
|
|
||||||
throw PowerDNSAPIException(error)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PowerDNSAPIException::class)
|
@Throws(PowerDNSAPIException::class)
|
||||||
fun createRecord(zoneName: String, recordName: String, recordType: String, recordContent: String, ttl: Int = 300, priority: Int = 0): Response {
|
fun createRecord(zoneName: String, recordName: String, recordType: String, recordContent: String, ttl: Int = 300, priority: Int = 0) {
|
||||||
val rrset = mapOf(
|
val rrset = mapOf(
|
||||||
"name" to recordName,
|
"name" to recordName,
|
||||||
"type" to recordType,
|
"type" to recordType,
|
||||||
@@ -95,17 +95,11 @@ class PowerDNSAPIClient() {
|
|||||||
.patch(body)
|
.patch(body)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
execute(request)
|
||||||
if(!response.isSuccessful) {
|
|
||||||
val str = response.body?.string()
|
|
||||||
val error = gson.fromJson(str, PowerDNSAPIError::class.java)
|
|
||||||
throw PowerDNSAPIException(error)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PowerDNSAPIException::class)
|
@Throws(PowerDNSAPIException::class)
|
||||||
fun updateRecord(zoneName: String, recordName: String, recordType: String, recordContent: String, ttl: Int = 300, priority: Int = 0): Response {
|
fun updateRecord(zoneName: String, recordName: String, recordType: String, recordContent: String, ttl: Int = 300, priority: Int = 0) {
|
||||||
val rrset = mapOf(
|
val rrset = mapOf(
|
||||||
"name" to recordName,
|
"name" to recordName,
|
||||||
"type" to recordType,
|
"type" to recordType,
|
||||||
@@ -127,17 +121,11 @@ class PowerDNSAPIClient() {
|
|||||||
.patch(body)
|
.patch(body)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
execute(request)
|
||||||
if(!response.isSuccessful) {
|
|
||||||
val str = response.body?.string()
|
|
||||||
val error = gson.fromJson(str, PowerDNSAPIError::class.java)
|
|
||||||
throw PowerDNSAPIException(error)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(PowerDNSAPIException::class)
|
@Throws(PowerDNSAPIException::class)
|
||||||
fun deleteRecord(zoneName: String, recordName: String, recordType: String): Response {
|
fun deleteRecord(zoneName: String, recordName: String, recordType: String) {
|
||||||
val rrset = mapOf(
|
val rrset = mapOf(
|
||||||
"name" to recordName,
|
"name" to recordName,
|
||||||
"type" to recordType,
|
"type" to recordType,
|
||||||
@@ -153,13 +141,7 @@ class PowerDNSAPIClient() {
|
|||||||
.patch(body)
|
.patch(body)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val response = client.newCall(request).execute()
|
execute(request)
|
||||||
if(!response.isSuccessful) {
|
|
||||||
val str = response.body?.string()
|
|
||||||
val error = gson.fromJson(str, PowerDNSAPIError::class.java)
|
|
||||||
throw PowerDNSAPIException(error)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class RecordController(
|
|||||||
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
|
||||||
])
|
])
|
||||||
fun allRecords(@PathVariable zone_id: String): ApiResponseDTO<List<RecordResponseDTO>> {
|
fun allRecords(@PathVariable zone_id: String): ApiResponseDTO<List<RecordResponseDTO>> {
|
||||||
return ApiResponseDTO(result = recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf())
|
return ApiResponseDTO(result = recordService.getRecordsByDomain(zone_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{zone_id}/dns_records/{dns_record_id}")
|
@GetMapping("{zone_id}/dns_records/{dns_record_id}")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
interface DomainRepository : JpaRepository<Domain?, Long?> {
|
interface DomainRepository : JpaRepository<Domain, Long> {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun findByCfid(cfid: String): Optional<Domain>
|
fun findByCfid(cfid: String): Optional<Domain>
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import org.springframework.data.jpa.repository.JpaRepository
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
interface RecordRepository : JpaRepository<Record?, Long?> {
|
interface RecordRepository : JpaRepository<Record, Long> {
|
||||||
@Transactional
|
@Transactional
|
||||||
fun findByCfid(cfid: String): Optional<Record>
|
fun findByCfid(cfid: String): Optional<Record>
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun findByDomainIdAndCfid(domainId: Long, cfid: String): Optional<Record>
|
fun findByDomainIdAndCfid(domainId: Long, cfid: String): Optional<Record>
|
||||||
|
|
||||||
|
fun findAllByDomainId(domainId: Long): List<Record>
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
fun deleteByCfid(cfid: String): Int
|
fun deleteByCfid(cfid: String): Int
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ data class User(
|
|||||||
@Column(nullable = false, unique = false, length = 20)
|
@Column(nullable = false, unique = false, length = 20)
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.EAGER)
|
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
val domains: List<Domain> = mutableListOf(),
|
val domains: List<Domain> = mutableListOf(),
|
||||||
) {
|
) {
|
||||||
override fun toString(): String = "User(id=$id, name='$name')"
|
override fun toString(): String = "User(id=$id, name='$name')"
|
||||||
|
|||||||
@@ -35,4 +35,5 @@ class UserFilter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentUser(): User =
|
fun getCurrentUser(): User =
|
||||||
SecurityContextHolder.getContext().authentication.principal as User
|
SecurityContextHolder.getContext().authentication?.principal as? User
|
||||||
|
?: throw IllegalStateException("Authenticated user is required")
|
||||||
@@ -20,7 +20,6 @@ class DomainService(
|
|||||||
try {
|
try {
|
||||||
val user = getCurrentUser()
|
val user = getCurrentUser()
|
||||||
val domain = domainRepository.findAllByUser(user)
|
val domain = domainRepository.findAllByUser(user)
|
||||||
if (domain.isEmpty()) throw RuntimeException("Unauthorized")
|
|
||||||
|
|
||||||
return domain
|
return domain
|
||||||
} catch(ex: Error) {
|
} catch(ex: Error) {
|
||||||
@@ -62,11 +61,14 @@ class DomainService(
|
|||||||
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
throw RuntimeException("Domain with CFID $domain_id not found")
|
throw RuntimeException("Domain with CFID $domain_id not found")
|
||||||
}
|
}
|
||||||
|
val user = getCurrentUser()
|
||||||
|
if (domain.user.id != user.id)
|
||||||
|
throw RuntimeException("Unauthorized to delete domain in API: $domain_id")
|
||||||
|
|
||||||
powerDNSApiClient.deleteZone(domain.name)
|
powerDNSApiClient.deleteZone(domain.name)
|
||||||
val count = domainRepository.deleteByCfid(domain_id)
|
val count = domainRepository.deleteByCfid(domain_id)
|
||||||
|
|
||||||
if (count > 0) throw RuntimeException("Domain with CFID $domain_id not found")
|
if (count == 0) throw RuntimeException("Domain with CFID $domain_id not found")
|
||||||
return domain_id
|
return domain_id
|
||||||
} catch(ex: Error) {
|
} catch(ex: Error) {
|
||||||
throw ex
|
throw ex
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class RecordService(
|
|||||||
proxied = false,
|
proxied = false,
|
||||||
ttl = record.ttl,
|
ttl = record.ttl,
|
||||||
locked = false,
|
locked = false,
|
||||||
zoneId = record.domain.cfid,
|
zoneId = domain.cfid,
|
||||||
zoneName = domain.name,
|
zoneName = domain.name,
|
||||||
createdOn = record.createdOn.getISOFormat(),
|
createdOn = record.createdOn.getISOFormat(),
|
||||||
modifiedOn = record.modifiedOn.getISOFormat(),
|
modifiedOn = record.modifiedOn.getISOFormat(),
|
||||||
@@ -82,8 +82,9 @@ class RecordService(
|
|||||||
if(domain.user.id != user.id)
|
if(domain.user.id != user.id)
|
||||||
throw RuntimeException("Unauthorized to get record in API: $domain_id")
|
throw RuntimeException("Unauthorized to get record in API: $domain_id")
|
||||||
|
|
||||||
val record = domain.records.find { it.cfid == record_id }
|
val record = recordRepository.findByDomainIdAndCfid(domain.id!!, record_id).orElseThrow {
|
||||||
if(record == null) throw RuntimeException("Failed to find record in API: $record_id")
|
RuntimeException("Failed to find record in API: $record_id")
|
||||||
|
}
|
||||||
|
|
||||||
return RecordResponseDTO(
|
return RecordResponseDTO(
|
||||||
id = record.cfid,
|
id = record.cfid,
|
||||||
@@ -91,15 +92,15 @@ class RecordService(
|
|||||||
name = record.name,
|
name = record.name,
|
||||||
content = record.content,
|
content = record.content,
|
||||||
ttl = record.ttl,
|
ttl = record.ttl,
|
||||||
zoneId = record.domain.cfid,
|
zoneId = domain.cfid,
|
||||||
zoneName = record.domain.name,
|
zoneName = domain.name,
|
||||||
createdOn = record.createdOn.getISOFormat(),
|
createdOn = record.createdOn.getISOFormat(),
|
||||||
modifiedOn = record.modifiedOn.getISOFormat(),
|
modifiedOn = record.modifiedOn.getISOFormat(),
|
||||||
comment = record.comment,
|
comment = record.comment,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRecordsByDomain(domain_id: String): List<RecordResponseDTO>? {
|
fun getRecordsByDomain(domain_id: String): List<RecordResponseDTO> {
|
||||||
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
val domain = domainRepository.findByCfid(domain_id).orElseThrow {
|
||||||
RuntimeException("Failed to find domain in API: $domain_id")
|
RuntimeException("Failed to find domain in API: $domain_id")
|
||||||
}
|
}
|
||||||
@@ -108,13 +109,13 @@ class RecordService(
|
|||||||
if(domain.user.id != user.id)
|
if(domain.user.id != user.id)
|
||||||
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
throw RuntimeException("Unauthorized to create record in API: $domain_id")
|
||||||
|
|
||||||
return domain?.records?.map { RecordResponseDTO(
|
return recordRepository.findAllByDomainId(domain.id!!).map { RecordResponseDTO(
|
||||||
id = it.cfid,
|
id = it.cfid,
|
||||||
type = it.type,
|
type = it.type,
|
||||||
name = it.name,
|
name = it.name,
|
||||||
content = it.content,
|
content = it.content,
|
||||||
zoneId = it.domain.cfid,
|
zoneId = domain.cfid,
|
||||||
zoneName = it.domain.name,
|
zoneName = domain.name,
|
||||||
priority = it.prio,
|
priority = it.prio,
|
||||||
ttl = it.ttl,
|
ttl = it.ttl,
|
||||||
createdOn = it.createdOn.getISOFormat(),
|
createdOn = it.createdOn.getISOFormat(),
|
||||||
@@ -153,7 +154,7 @@ class RecordService(
|
|||||||
else -> record.ttl
|
else -> record.ttl
|
||||||
}
|
}
|
||||||
|
|
||||||
powerDNSApiClient.updateRecord(domain!!.name, updatedRecord.name, updatedRecord.type, updatedRecord.value)
|
powerDNSApiClient.updateRecord(domain.name, updatedRecord.name, updatedRecord.type, updatedRecord.value, record.ttl, record.prio)
|
||||||
|
|
||||||
// 저장
|
// 저장
|
||||||
val savedRecord = recordRepository.save(record)
|
val savedRecord = recordRepository.save(record)
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package space.mori.dnsapi
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.springframework.boot.test.context.SpringBootTest
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.ActiveProfiles
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("test")
|
||||||
class DnsapiApplicationTests {
|
class DnsapiApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package space.mori.dnsapi
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import org.junit.jupiter.api.Tag
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest
|
||||||
|
import org.springframework.test.context.ActiveProfiles
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders
|
||||||
|
import org.springframework.web.context.WebApplicationContext
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
@Tag("openapi-generation")
|
||||||
|
@SpringBootTest(properties = ["springdoc.api-docs.enabled=true"])
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
class OpenApiGenerationTest(@Autowired private val context: WebApplicationContext) {
|
||||||
|
@Test
|
||||||
|
fun generateOpenApi() {
|
||||||
|
val response = MockMvcBuilders.webAppContextSetup(context).build()
|
||||||
|
.perform(get("/api-docs").with { request ->
|
||||||
|
request.serverName = "localhost"
|
||||||
|
request.serverPort = 8080
|
||||||
|
request
|
||||||
|
})
|
||||||
|
.andReturn().response
|
||||||
|
check(response.status == 200) { "OpenAPI endpoint returned HTTP ${response.status}: ${response.contentAsString}" }
|
||||||
|
|
||||||
|
val output = Path.of(System.getProperty("openapi.output"))
|
||||||
|
val json = GsonBuilder().setPrettyPrinting().create()
|
||||||
|
.toJson(JsonParser.parseString(response.contentAsString))
|
||||||
|
Files.writeString(output, json + "\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package space.mori.dnsapi
|
||||||
|
|
||||||
|
import mockwebserver3.MockResponse
|
||||||
|
import mockwebserver3.MockWebServer
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils
|
||||||
|
|
||||||
|
class PowerDNSAPIClientTest {
|
||||||
|
@Test
|
||||||
|
fun successfulCallsUseTheExpectedHttpMethods() {
|
||||||
|
MockWebServer().use { server ->
|
||||||
|
server.start()
|
||||||
|
repeat(5) { server.enqueue(MockResponse.Builder().body("ok").build()) }
|
||||||
|
val client = clientFor(server)
|
||||||
|
|
||||||
|
client.createZone("example.com")
|
||||||
|
client.deleteZone("example.com")
|
||||||
|
client.createRecord("example.com", "www.example.com.", "A", "192.0.2.1")
|
||||||
|
client.updateRecord("example.com", "www.example.com.", "A", "192.0.2.2")
|
||||||
|
client.deleteRecord("example.com", "www.example.com.", "A")
|
||||||
|
|
||||||
|
assertEquals("POST", server.takeRequest().method)
|
||||||
|
assertEquals("DELETE", server.takeRequest().method)
|
||||||
|
repeat(3) { assertEquals("PATCH", server.takeRequest().method) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun errorResponsesKeepThePowerDnsMessageAndHandleInvalidJson() {
|
||||||
|
MockWebServer().use { server ->
|
||||||
|
server.start()
|
||||||
|
server.enqueue(MockResponse.Builder().code(400)
|
||||||
|
.body("{\"error\":\"invalid zone\",\"errors\":[\"bad name\"]}").build())
|
||||||
|
server.enqueue(MockResponse.Builder().code(500).body("not json").build())
|
||||||
|
val client = clientFor(server)
|
||||||
|
|
||||||
|
val error = assertThrows(PowerDNSAPIException::class.java) { client.deleteZone("example.com") }
|
||||||
|
assertEquals("invalid zone", error.message)
|
||||||
|
assertEquals(listOf("bad name"), error.errors)
|
||||||
|
|
||||||
|
val fallback = assertThrows(PowerDNSAPIException::class.java) { client.deleteZone("example.com") }
|
||||||
|
assertEquals("PowerDNS API returned HTTP 500", fallback.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clientFor(server: MockWebServer): PowerDNSAPIClient = PowerDNSAPIClient().also {
|
||||||
|
ReflectionTestUtils.setField(it, "apiUrl", server.url("/").toString().removeSuffix("/"))
|
||||||
|
ReflectionTestUtils.setField(it, "apiKey", "test-key")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package space.mori.dnsapi.service
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertThrows
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder
|
||||||
|
import space.mori.dnsapi.PowerDNSAPIClient
|
||||||
|
import space.mori.dnsapi.db.Domain
|
||||||
|
import space.mori.dnsapi.db.DomainRepository
|
||||||
|
import space.mori.dnsapi.db.User
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
class DomainServiceTest {
|
||||||
|
private val repository = Mockito.mock(DomainRepository::class.java)
|
||||||
|
private val powerDns = Mockito.mock(PowerDNSAPIClient::class.java)
|
||||||
|
private val service = DomainService(repository, powerDns)
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun clearSecurityContext() {
|
||||||
|
SecurityContextHolder.clearContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rejectsDeletingAnotherUsersDomainBeforeCallingPowerDns() {
|
||||||
|
val owner = User(id = 1, apiKey = "owner", name = "owner")
|
||||||
|
val otherUser = User(id = 2, apiKey = "other", name = "other")
|
||||||
|
val domain = Domain(name = "example.com", user = owner)
|
||||||
|
Mockito.`when`(repository.findByCfid(domain.cfid)).thenReturn(Optional.of(domain))
|
||||||
|
authenticate(otherUser)
|
||||||
|
|
||||||
|
assertThrows(RuntimeException::class.java) { service.deleteDomain(domain.cfid) }
|
||||||
|
Mockito.verifyNoInteractions(powerDns)
|
||||||
|
Mockito.verify(repository, Mockito.never()).deleteByCfid(domain.cfid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deletingAnOwnedDomainSucceedsWhenOneRowWasRemoved() {
|
||||||
|
val owner = User(id = 1, apiKey = "owner", name = "owner")
|
||||||
|
val domain = Domain(name = "example.com", user = owner)
|
||||||
|
Mockito.`when`(repository.findByCfid(domain.cfid)).thenReturn(Optional.of(domain))
|
||||||
|
Mockito.`when`(repository.deleteByCfid(domain.cfid)).thenReturn(1)
|
||||||
|
authenticate(owner)
|
||||||
|
|
||||||
|
assertEquals(domain.cfid, service.deleteDomain(domain.cfid))
|
||||||
|
Mockito.verify(powerDns).deleteZone(domain.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun aUserWithoutDomainsGetsAnEmptyList() {
|
||||||
|
val user = User(id = 1, apiKey = "owner", name = "owner")
|
||||||
|
Mockito.`when`(repository.findAllByUser(user)).thenReturn(emptyList())
|
||||||
|
authenticate(user)
|
||||||
|
|
||||||
|
assertEquals(emptyList<Domain>(), service.getAllDomains())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun authenticate(user: User) {
|
||||||
|
SecurityContextHolder.getContext().authentication =
|
||||||
|
UsernamePasswordAuthenticationToken(user, null, emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
spring.datasource.url=jdbc:h2:mem:dnsapi;DB_CLOSE_DELAY=-1
|
||||||
|
spring.datasource.driver-class-name=org.h2.Driver
|
||||||
|
spring.datasource.username=sa
|
||||||
|
spring.datasource.password=
|
||||||
|
spring.jpa.hibernate.ddl-auto=create-drop
|
||||||
|
pdns.api.key=test
|
||||||
|
pdns.api.url=http://localhost
|
||||||
|
pdns.ns=ns.example
|
||||||
Reference in new issue
Block a user