Compare commits

..
33 Commits
Author SHA1 Message Date
2martens 9aa199103d Don't add servers item 2023-08-19 18:34:25 +02:00
2martens 904bc60ed7 Added forwarded header filter as bean 2023-08-19 18:18:28 +02:00
2martens d5691e3081 Use framework handling of forward headers 2023-08-19 17:33:03 +02:00
2martens 538b1badec Add forward headers strategy 2023-08-19 16:59:05 +02:00
2martens d34ca9aff2 Removed not working step 2023-08-19 16:12:09 +02:00
2martens cd79f15d5b Enabled target and removed custom trigger 2023-08-19 03:54:43 +02:00
2martens 37506b6c83 Added promotion step 2023-08-19 03:54:11 +02:00
2martens 406a891849 Improved code style of Kotlin 2023-08-19 03:53:40 +02:00
2martens b411c97f53 Adjusted API to be more in line with resource orientation 2023-08-19 03:52:50 +02:00
2martens 37f57f788b Do not overwrite existing collections
continuous-integration/drone Build is failing
continuous-integration/drone/promote/production Build is passing
2023-08-17 23:56:56 +02:00
2martens d5c7597a04 Add cache to minimize time for installing ansible collections
continuous-integration/drone Build is failing
2023-08-17 23:53:39 +02:00
2martens 8f31d2dd3d Move to private_ssh_key as secret
continuous-integration/drone Build is failing
2023-08-17 23:49:06 +02:00
2martens 5ff47de10a Prevent commit if nothing changed
continuous-integration/drone Build is failing
2023-08-17 23:39:39 +02:00
2martens 794a08c9b0 Only run code quality on pull request
continuous-integration/drone Build is failing
2023-08-17 23:24:41 +02:00
2martens d579004653 Don't limit to target
continuous-integration/drone/push Build was killed
continuous-integration/drone Build was killed
2023-08-17 23:22:30 +02:00
2martens e127d66a1b Remove custom trigger for building docker container
continuous-integration/drone/push Build is failing
2023-08-17 23:21:36 +02:00
2martens 21fa17e62b Added galaxy requirements
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is failing
2023-08-17 23:20:14 +02:00
2martens 8381c1ba7d Use Drone semver
continuous-integration/drone/push Build is failing
2023-08-17 23:07:16 +02:00
2martens 4308130cd1 Fixed usage of image version
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 23:03:00 +02:00
2martens 721cdac95d Use latest ansible plugin version
continuous-integration/drone/push Build is failing
2023-08-17 22:59:49 +02:00
2martens 395fa52962 Fixed pipeline
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 22:50:20 +02:00
2martens 45e5c473ff Added pipeline to deploy via Ansible
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 21:11:44 +02:00
2martens 1c3648f0f4 Update [CI SKIP] 2023-08-17 12:51:18 +02:00
2martens 5b707728b8 Change properties [CI SKIP] 2023-08-17 02:18:08 +02:00
2martens c19c211839 Make properties refreshable
continuous-integration/drone/push Build is failing
2023-08-17 02:15:23 +02:00
2martens aace535914 Change message [CI SKIP] 2023-08-17 02:13:07 +02:00
2martens 59982ef762 Remove refresh scope from Clock
continuous-integration/drone/push Build is failing
2023-08-17 02:08:12 +02:00
2martens 56c6fef178 Remove constructor binding
continuous-integration/drone/push Build is failing
2023-08-17 02:02:19 +02:00
2martens 46b1b32708 Properties must be changeable with setters
continuous-integration/drone/push Build is failing
2023-08-17 01:52:50 +02:00
2martens 519101023c Change message [CI SKIP] 2023-08-17 01:42:01 +02:00
2martens 82b4f51390 Require configserver on prod
continuous-integration/drone/push Build is failing
2023-08-17 01:38:26 +02:00
2martens 3256939ac7 Change message [CI SKIP] 2023-08-17 01:20:17 +02:00
2martens 02e4c5318d Fix bootstrap servers on prod
continuous-integration/drone/push Build is failing
2023-08-17 01:14:03 +02:00
14 changed files with 294 additions and 151 deletions
+76 -7
View File
@@ -30,7 +30,6 @@ volumes:
trigger:
event:
include:
- push
- pull_request
- custom
@@ -51,7 +50,7 @@ steps:
image: alpine/git
environment:
SSH_KEY:
from_secret: ssh_key
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
@@ -69,7 +68,7 @@ steps:
PASSWORD:
from_secret: docker_password
SSH_KEY:
from_secret: ssh_key
from_secret: private_ssh_key
volumes:
- name: cache
path: /home/gradle/.gradle
@@ -88,10 +87,80 @@ volumes:
path: /var/lib/drone/cache/.gradle
trigger:
branches:
- main
event:
include:
- push
- custom
- tag
---
kind: pipeline
name: deploy image
type: docker
platform:
os: linux
arch: arm64
clone:
disable: true
steps:
- name: clone
image: alpine/git
environment:
SSH_KEY:
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
- chmod 600 $HOME/.ssh/id_ed25519
- touch $HOME/.ssh/known_hosts
- chmod 600 $HOME/.ssh/known_hosts
- ssh-keyscan -H git.2martens.de > $HOME/.ssh/known_hosts 2>/dev/null
- git clone ssh://giteajim@git.2martens.de:22/2martens/cloud-configuration.git .
- git checkout main
- name: modify deployed image
image: alpine
commands:
- sed -i -r "s/(wahlrecht_image_version:).*/\1\ ${DRONE_SEMVER}/" ansible/inventories/host_vars/api-server/public.yaml
- name: save modified variable file
image: alpine/git
environment:
SSH_KEY:
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
- chmod 600 $HOME/.ssh/id_ed25519
- touch $HOME/.ssh/known_hosts
- chmod 600 $HOME/.ssh/known_hosts
- ssh-keyscan -H git.2martens.de > $HOME/.ssh/known_hosts 2>/dev/null
- git add ansible/inventories/host_vars/api-server/public.yaml
- git diff-index --quiet HEAD || git commit -m "[Drone] Changed wahlrecht_image_version to ${DRONE_SEMVER}"
- git push origin main
- name: deploy image
image: plugins/ansible:latest
volumes:
- name: cache
path: /root/.ansible
settings:
playbook: ansible/deploy_spring.yaml
inventory: ansible/inventories/production
galaxy: ansible/requirements.yaml
galaxy_force: false
private_key:
from_secret: private_ssh_key
vault_password:
from_secret: vault_password
volumes:
- name: cache
host:
path: /var/lib/drone/cache/.ansible
trigger:
target:
- production
event:
include:
- promote
- rollback
+3 -3
View File
@@ -543,9 +543,9 @@ ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
[{*.kt,*.kts}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 4
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
@@ -0,0 +1,13 @@
package de.twomartens.wahlrecht.configuration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.filter.ForwardedHeaderFilter
@Configuration
open class FilterConfiguration {
@Bean
open fun forwardedFilter(): ForwardedHeaderFilter {
return ForwardedHeaderFilter()
}
}
@@ -6,7 +6,6 @@ import io.swagger.v3.oas.annotations.security.OAuthFlows
import io.swagger.v3.oas.annotations.security.SecurityScheme
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.servers.Server
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@@ -36,7 +35,5 @@ open class OpenApiConfiguration {
.version(apiVersion)
.description(apiDescription)
)
.addServersItem(Server().url("http://localhost:12000"))
.addServersItem(Server().url("https://api.2martens.de"))
}
}
@@ -12,10 +12,12 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@RestController
@RequestMapping(value = ["/wahlrecht/v1"])
@@ -58,14 +60,14 @@ class ElectionController(private val service: ElectionService) {
content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))]
)]
)
@GetMapping(value = ["/elections/by-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@GetMapping(value = ["/elections/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun getElectionByName(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String?
): ResponseEntity<Election> {
val election = mapper.mapToExternal(service.elections[electionName]!!)
return ResponseEntity.ok()
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(election)
}
@@ -84,8 +86,14 @@ class ElectionController(private val service: ElectionService) {
@PutMapping("/election")
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun putElection(@RequestBody election: Election?): ResponseEntity<*> {
fun putElection(@RequestBody election: Election?, uriComponentsBuilder: UriComponentsBuilder):
ResponseEntity<Void> {
val createdNew = service.storeElection(mapper.mapToDB(election!!))
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
return if (createdNew) {
created(uriComponentsBuilder.path("/elections/{electionName}")
.buildAndExpand(election.name).toUri())
.build()
}
else ok().build()
}
}
@@ -14,10 +14,12 @@ import io.swagger.v3.oas.annotations.tags.Tag
import lombok.RequiredArgsConstructor
import lombok.extern.slf4j.Slf4j
import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@Slf4j
@RestController
@@ -40,16 +42,16 @@ class ElectionResultController(private val service: ElectionResultService) {
)]
)
@GetMapping(
value = ["/electionResult/by-election-name/{electionName}"],
value = ["/electionResults/{electionName}"],
produces = [MediaType.APPLICATION_JSON_VALUE]
)
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun getElectionByName(
fun getElectionResultByElectionName(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String
): ResponseEntity<ElectionResult> {
val result = mapper.mapToExternal(service.getElectionResult(electionName))
return ResponseEntity.ok()
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(result)
}
@@ -68,8 +70,14 @@ class ElectionResultController(private val service: ElectionResultService) {
@PutMapping("/electionResult")
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun putElection(@RequestBody electionResult: ElectionResult?): ResponseEntity<*> {
fun putElection(@RequestBody electionResult: ElectionResult?,
uriComponentsBuilder: UriComponentsBuilder): ResponseEntity<Void> {
val createdNew = service.storeResult(mapper.mapToDB(electionResult!!))
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
return if (createdNew) {
created(uriComponentsBuilder.path("/electionResult/{electionName}")
.buildAndExpand(electionResult.electionName).toUri())
.build()
}
else ok().build()
}
}
@@ -13,17 +13,20 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@Controller
@RequestMapping(value = ["/wahlrecht/v1"])
@Tag(name = "Parties", description = "all requests relating to parties")
class PartyController(private val service: PartyService) {
private val mapper = Mappers.getMapper(PartyInElectionMapper::class.java)
@Operation(
summary = "Returns all stored parties for given election name",
responses = [ApiResponse(
@@ -39,7 +42,7 @@ class PartyController(private val service: PartyService) {
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))]
)]
)
@GetMapping(value = ["/parties/by-election-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@GetMapping(value = ["/parties/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun getPartiesByElectionName(
@@ -52,11 +55,41 @@ class PartyController(private val service: PartyService) {
)
}
.toList()
return ResponseEntity.ok()
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(parties)
}
@Operation(
summary = "Returns stored party for given election name and party abbreviation",
responses = [ApiResponse(
responseCode = "200",
description = "Returns stored party",
content = [Content(
mediaType = "application/json",
schema = Schema(implementation = PartyInElection::class)
)]
), ApiResponse(
responseCode = "404",
description = "No party found",
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))]
)]
)
@GetMapping(value = ["/parties/{electionName}/{abbreviation}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun getPartyByElectionNameAndAbbreviation(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String,
@PathVariable @Parameter(description = "the party abbreviation", example = "SPD") abbreviation: String
): ResponseEntity<PartyInElection> {
val party = mapper.mapToExternal(
service.getPartyByElectionNameAndAbbreviation(electionName, abbreviation)
)
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(party)
}
@Operation(
summary = "Stores a new party in an election",
responses = [ApiResponse(
@@ -70,8 +103,13 @@ class PartyController(private val service: PartyService) {
@PutMapping("/party")
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun putParty(@RequestBody party: PartyInElection?): ResponseEntity<*> {
fun putParty(@RequestBody party: PartyInElection?,
uriComponentsBuilder: UriComponentsBuilder): ResponseEntity<Void> {
val createdNew = service.storeParty(mapper.mapToDB(party!!))
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
return if (createdNew) {
created(uriComponentsBuilder.path("/parties/{electionName}/{abbreviation}")
.buildAndExpand(party.electionName, party.abbreviation).toUri())
.build()
} else ok().build()
}
}
@@ -2,11 +2,13 @@ package de.twomartens.wahlrecht.property
import io.swagger.v3.oas.annotations.media.Schema
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.cloud.context.config.annotation.RefreshScope
import java.time.ZoneId
@RefreshScope
@ConfigurationProperties(prefix = "de.twomartens.wahlrecht")
@Schema(description = "Properties, to configure this Application")
data class ServiceProperties @ConstructorBinding constructor(val defaultTimeZone: ZoneId, val greeting: String)
class ServiceProperties {
lateinit var defaultTimeZone: ZoneId
lateinit var greeting: String
}
@@ -5,4 +5,6 @@ import org.springframework.data.mongodb.repository.MongoRepository
interface PartyRepository : MongoRepository<PartyInElection, String> {
fun findByElectionName(name: String): Collection<PartyInElection>
fun findByElectionNameAndAbbreviation(electionName: String, abbreviation: String): PartyInElection?
}
@@ -18,6 +18,13 @@ class PartyService(
fetchParties()
}
fun getPartyByElectionNameAndAbbreviation(electionName: String,
abbreviation: String): PartyInElection {
return partyRepository.findByElectionNameAndAbbreviation(electionName, abbreviation)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND,
"no party found for $electionName and $abbreviation")
}
fun getPartiesByElectionName(electionName: String): Collection<PartyInElection> {
val parties = partyRepository.findByElectionName(electionName)
if (parties.isEmpty()) {
@@ -1,5 +1,7 @@
spring:
config:
import:
- "classpath:config/wahlrecht.yaml"
- "optional:configserver:http://configserver:8888/config"
- "configserver:http://configserver:8888/config"
kafka:
bootstrap-servers:
- kafka:9092
@@ -1,6 +1,7 @@
server:
port: 12000
shutdown: graceful
forward-headers-strategy: framework
### technical configurations ###
management:
@@ -28,10 +29,6 @@ spring:
name: wahlrecht
main:
banner-mode: off
kafka:
bootstrap-servers:
- localhost:9092
- kafka:9092
profiles:
default: dev
data.mongodb:
@@ -1,2 +1,2 @@
de.twomartens.wahlrecht:
greeting: "Moin %s, you are prod"
greeting: "Moin %s, you are test"