An open API service indexing awesome lists of open source software.

https://github.com/integraal-klant-en-objectbeeld/iko

Welcome at the IKO (Integraal Klant & Objectbeeld) repository.
https://github.com/integraal-klant-en-objectbeeld/iko

apache-camel carbon-design-system docker-compose htmx jq oidc-provider spring-mvc thymeleaf-template-engine

Last synced: 4 months ago
JSON representation

Welcome at the IKO (Integraal Klant & Objectbeeld) repository.

Awesome Lists containing this project

README

          

Iko Logo

### Integraal Klant & Objectbeeld (IKO)

![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square)

IKO is a Kotlin/Spring Boot application that uses Apache Camel to integrate with external systems via connectors. It ships with an admin UI and a Docker-based local environment.

---

### Features
- Spring Boot 3.5.x, Kotlin 2.2, JDK 21
- OAuth2 (Keycloak) for admin login and JWT resource server
- Redis caching for improved performance
- Prometheus metrics endpoint for monitoring
- Dockerfile for containerized runs and GitHub Actions CI/CD

---

### Prerequisites
- Docker and Docker Compose
- GitHub account (to use CI/CD workflows)
- For local development: JDK 21 and Gradle wrapper

---

### Quick start with Docker
1. Start the local stack:
```bash
docker compose up -d
```
2. Add a hosts entry so Keycloak tokens use the hostname expected by configuration:
```text
127.0.0.1 keycloak
```
3. Open the admin UI: `http://localhost:8080/admin`

Notes:
- The shipped `Dockerfile` (repo root) builds a runnable IKO container.
- The compose file provides dependent services and sets up Keycloak for local login.

---

### Local development
- Copy `.env.template` to `.env` and adjust values to your local setup.
- For HTML live-refresh during local dev only, `.env.template` includes:
```
SPRING_THYMELEAF_PREFIX=file:src/main/resources/templates/
# SPRING_WEB_RESOURCES_STATIC-LOCATIONS=file:src/main/resources/static/
```
Remove or comment these when running inside Docker (the application defaults to classpath locations in `application.yml`).
- Run the app via Gradle (env injected by the Dotenv Gradle plugin):
```bash
./gradlew bootRun
```

---

### Configuration overview
- Main config: `src/main/resources/application.yml`
- Disables OSIV (`spring.jpa.open-in-view=false`)
- Camel YAML routes path: `classpath:camel/*.yaml`
- Security: OAuth2/OIDC login for admin (roles claim + required admin roles configurable via `iko.security.admin.rolesClaim` / `iko.security.admin.authorities`) and JWT resource server for APIs (authorities from claim `resource_access.iko.roles`)

---

### Build and test
- Run tests:
```bash
./gradlew test
```
- Build the application JAR:
```bash
./gradlew build
```
- Build a local Docker image:
```bash
docker build -t iko:local .
```

---

### Admin UI
- URL: `http://localhost:8080/admin`
- Tutorial (Aggregated Data Profile):
https://docs.integraal-klant-objectbeeld.nl/admin-configuratie/samengesteld-gegevensprofiel-aanmaken

---

### CI/CD

#### Validate Build (`.github/workflows/validate-build.yml`)
- Triggered on `pull_request` targeting the `main` branch.
- Runs gradle build on the sources to make sure the sources build.
- Is a requirement for merging a pull request to the `main` branch.
- Failing builds will not be allowed to merge.

#### Snapshot workflow (`.github/workflows/snapshot-releases.yml`)
- Triggered on `push` to the `main` branch or a manual `workflow_dispatch` against a chosen branch.
- Builds the Docker image with Buildx (single-arch `linux/amd64`).
- On `push` to `main`, pushes to GHCR (`ghcr.io//`) with tags such as:
- `main` (default branch)
- `sha-`
- `snapshot-YYYYMMDDHHmm`
- `branch-` for non-main branches
- Adds OCI labels/annotations (title, description, revision, source, created).

#### Manual release workflow (`.github/workflows/start-manual-release.yml`)
This workflow is used to cut a formal release that publishes a versioned container image and a GitHub Release.

- Trigger: manually via the Actions tab (`workflow_dispatch`).
- Input: `version` — a semantic version like `1.2.3` or `1.2.3-beta.1` (no leading `v`). The workflow validates this with a semver regex.
- Permissions: `contents: write` (create GitHub Release) and `packages: write` (push to GHCR).

How it works (two-job pipeline):
1. `build` job
- Checks out code, sets up QEMU and Buildx.
- Extracts Docker metadata for consistent OCI labels.
- Builds the Docker image once and exports it as a `docker`-format TAR (`iko-image.tar`) tagged locally as `iko:iko-`.
- Uploads the TAR as a short-lived artifact.
2. `push` job
- Downloads the image artifact and logs into GHCR with `GITHUB_TOKEN`.
- Loads the image from the TAR and retags it as:
- `ghcr.io//:`
- `ghcr.io//:latest`
- Pushes both tags.
- Creates a GitHub Release with tag `v` and autogenerated release notes. If the version contains a `-` (e.g., `-rc.1`), the release is marked as a prerelease.

When to use it:
- Use `start-manual-release.yml` when you are ready to publish a stable or pre-release version to consumers.
- Use `snapshot-releases.yml` for continuous builds on `main` or for verifying PRs.

How to run a manual release:
1. Go to GitHub → Actions → `Start release`.
2. Click `Run workflow` and enter a semver (e.g., `1.3.0` or `1.3.0-rc.1`).
3. Confirm to start. After completion you will have:
- GHCR images: `ghcr.io//:` and `:latest`.
- GitHub Release: tag `v` with autogenerated notes.

Notes and gotchas:
- Architecture: currently builds for `linux/amd64` only. Adjust `platforms` if you need multi-arch.
- Input version must be plain semver (e.g., `1.2.3`), not `v1.2.3`.
- The artifact retention is short (1 day) to avoid stale reuse; the image is built once and then retagged/pushed in the separate job.

---

### More documentation
You can find more documentation [here](./doc/README.md)

### Crypto
IKO uses AES encryption for config storage of connectors.
Please generate a Base64 key in the env var IKO_CRYPTO_KEY via the .env file for local development.

Kotlin notebook code helps run pieces of code or just ask AI.
```Kotlin
fun generateBase64AesKey(keySize: Int = 256): String {
val keyGen = KeyGenerator.getInstance("AES")
keyGen.init(keySize, SecureRandom())
val secretKey: SecretKey = keyGen.generateKey()
return Base64.getEncoder().encodeToString(secretKey.encoded)
}

fun main() {
val base64Key = generateBase64AesKey()
println("Generated AES key (Base64): $base64Key")
}
main()
```