Renaming gitTally to Werkator because there is another git-related tool named gittally (#1)
* renaming from gitTally to Werkator * Rename GitTally to Werkator `gitTally` is the name of another product in the git space, so the rename is a precaution; nothing about what the build system does changes. The name follows one rule: `Werkator` where it is prose, capitalized where it is a Kotlin type and its file, lowercase everywhere a machine reads it — the command, packages, paths, configuration keys and values, the Gitea check context. Environment variables keep their convention and are uppercase throughout. Every configuration file is still found under its pre-rename name (`ConfigFiles`): `.gittally.yml` at the repository root, in a build worktree and as committed on a branch, `.git/gittally/.gittally.yml` for the machine layer. The current name wins where both exist, and the old file is then ignored rather than merged — two files side by side are a half-done rename, not a layering. Without the fallback an installation that updated without renaming would not fail: a configuration that is not found leaves every setting at its default, so it would come up looking healthy while having forgotten its credentials and its builds. `docs/werkator-migrationsplan.md` lists what the fallback does not cover and has to be moved by hand — above all the state directory `.git/werkator/`, which holds the build history, the control token and the worktrees, and has no fallback of its own. `docs/migration-from-legacy.md` is deleted with this: it mapped the legacy script's environment variables, and every host it addressed has long since moved to the YAML configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Move the pre-rename state directory at the first start The configuration is found under either name, the state is not: build history, control token, auto-build slots and worktrees live at one fixed path. An installation that updates without moving `.git/gittally` would not fail — it would come up with an empty history and a fresh control token, quietly. So the first start moves it instead of the release notes asking for it. Only when the old directory exists and the new one does not. Where both exist nothing is touched and a warning names the leftover: which of the two is the live state is not something to guess. A failed move is an error in the log, never an abort — a CI must not hang on it. The worktrees are dropped rather than moved, since they point at their old path in both directions; `GitWorktreeWorkspaces` prunes the stale admin entry and recreates each on its branch's next build. A generated systemd unit moves with the directory and leaves its symlink dangling, which is warned about — the running service is unaffected, the next start is not. Runs from `CliRunner`, before any command resolves a path under the directory, and so before the second context of `server` exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Document PR#1: the rename to Werkator Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Drop the legacy env-to-YAML conversion from the setup tool The old bash script configured itself through `GITTALLY_*` environment variables. The blanket rename rewrote those literals, so the converter was looking for `WERKATOR_*` — a spelling no host has ever written. Fed a real legacy file it would have found nothing and written an almost empty configuration, without an error, which is the same silent failure this rename is otherwise careful to avoid. The conversion has served its purpose with the vm2176 to vm4006 migration, so it goes instead of being repaired. What remains is the setup of a new instance: the preconditions, the credential prompt, and the machine configuration written mode 600 — now carrying the host's public URL as well, since that is host-specific too. Everything the repository builds comes from `init` and its templates. It also stops emitting a legacy `branches:` section, which step 18 is about to reject outright. `docs/plan/00-legacy-analysis.md` and `13-nginx-tls.md` get the real `GITTALLY_*` spelling back: they record what the old script read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Describe this repository's build with a build definition Its own `.werkator.yml` still used the deprecated `branches` section with an `autoBuild` schedule that was switched off. That section is read only while nothing defines a build at all, and step 18 rejects it by name — so this repository would have blocked the precondition of that step, which asks that no configuration still in play carries it. Nothing about the build changes: `builds.default` with `trigger.onPush` is a build of every new commit on every branch, which is what the branch section said. `config:print --full` resolves the definition completely and logs no deprecation warning any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Stop documenting the pre-rename fallback for users Exactly one repository is configured with the old names, and it is migrated by hand in the same move as this release. The fallback is therefore a transition of days, not a feature anyone reading the release notes or the configuration reference has to plan around. Removed from `releases.html` and `docs/configuration.md`. The mechanism itself is unchanged and stays described where it is worked on: in `ConfigFiles`, in `StateDirMigration`, and in the migration plan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Bring the PR-doc to its final state The `statusContext` question is answered and marked as decided rather than left standing: it is the one value a human reads as a label, and it stays lowercase because Gitea matches it and the client reads it back, which makes it a value. Also records that the pre-rename fallback is deliberately absent from the release notes and the configuration reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Release v1.0.0: Werkator The release after 0.9.21 is 1.0.0, because a product that changes its name is better off counting from one under it. The release note says as much, so the jump is not read as a claim about maturity — plan steps 14, 17 and 18 are still open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Correct the PR-doc about the version It claimed the PR carries no version bump, which the release commit made untrue, and records why the number is 1.0.0 instead of 0.9.22. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Point the legacy references at the history `legacy/gitTally` was removed from the tree with the rename, but the README still described it as a reference kept in the repository, and the plan told an executing session to read parts of it — including step 14, which is open. The README section is gone; `docs/plan/README.md`, step 14 and the legacy analysis now say where the script actually is (`git show 7f55068^:legacy/gitTally`). Executed steps and ADR 0004 keep their wording: they record what was true when they ran. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Retarget the links in the historic PR-docs The package rename moved every file the older PR-docs link to, leaving 60 dead links. Only the link targets are rewritten, never the visible text and never a statement: those documents record what was true when they were written, GitTally in the prose included. A snapshot may be outdated; it should still be navigable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add the v1.0.0 deployment procedure for vm4006 Measured, not estimated: the state directory is 878 MB, of which 878 MB are the nine build worktrees. What cannot be recreated is 84 KB, so the snapshot before an in-place switch is instant and the rollback is one sequence of moves. Records the three expected non-failures — a cold Gradle volume, one image rebuild, containers left under the old label — and that `gitea.statusContext` needs no attention because it comes from the watched repository's committed configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Rename the machine configuration along with its directory Found by the deployment to vm4006: the move renames the directory and leaves the file inside it alone, so the machine configuration ended up at `.git/werkator/.gittally.yml` — a pair of names the lookup did not expect, because it pairs directory and file name. The instance resolved empty credentials, no public URL and none of the host's build definitions, and said nothing about it. That is the exact failure this change exists to prevent, produced by the change itself. `StateDirMigration` now renames the configuration with the directory, unless one under the current name is already there. `ConfigFiles` carries `.git/werkator/.gittally.yml` as a third candidate as well, for a directory somebody moved by hand, where the migration never runs and so can rename nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Let init see a configuration under its previous name `init --systemd` runs `init`, and its "already exists" check knew only the current name. On the one repository still carrying `.gittally.yml` it therefore wrote a fresh template `.werkator.yml` beside it — and since the current name wins, that repository would have built the template's `./gradlew test` instead of what its own configuration says. Found on vm4006, where the file was created in the watched working tree and removed again by hand. Both checks now ask `ConfigFiles`, so init decides existence by the same rule the loader uses to read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Record the v1.0.0 deployment to vm4006 Deployed from the branch as the final test of PR#1, and it did what a final test is for: it found two silent-failure defects before the service was started, both fixed and redeployed in the same window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Drop the control token left under the old localStorage key The key is named after the product, so the rename left every browser with a token under `gittally.controlToken`, which nothing reads any more and which "forget token" can no longer reach. It is a write-scope token in a browser store, not a password, but a secret nobody owns is worth one line to remove. Removed on load. The token on the server is unchanged, so re-entering it once per browser is all the rename costs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d76744721
commit
d0169e57bc
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally
|
||||
package de.hoennig.werkator
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.file.Files
|
||||
@@ -8,7 +8,7 @@ import java.nio.file.attribute.PosixFilePermissions
|
||||
|
||||
/**
|
||||
* Creation of files and directories that hold secrets — the Gitea token in
|
||||
* `.git/gittally/.gittally.yml` and the control token.
|
||||
* `.git/werkator/.werkator.yml` and the control token.
|
||||
*
|
||||
* The permissions are set *at creation*, never with a `chmod` after the write:
|
||||
* writing at the umask default first (typically `0644`) would leave a window in
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.hoennig.werkator
|
||||
|
||||
import de.hoennig.werkator.config.ConfigFiles
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Moves the state directory of an installation that predates the rename to Werkator,
|
||||
* once, at the first start after the update.
|
||||
*
|
||||
* The configuration is found under either name ([de.hoennig.werkator.config.ConfigFiles]),
|
||||
* but the state is not: build history, the control token, the auto-build slots and the
|
||||
* build worktrees live at one fixed path. A missing state directory is as quiet as a
|
||||
* missing configuration — the instance would come up with an empty history and a fresh
|
||||
* control token, and nothing would fail. So this is done rather than documented.
|
||||
*/
|
||||
object StateDirMigration {
|
||||
const val DIR = ".git/werkator"
|
||||
|
||||
private const val LEGACY_DIR = ".git/gittally"
|
||||
private const val WORKTREES = "worktrees"
|
||||
|
||||
private val log = LoggerFactory.getLogger(StateDirMigration::class.java)
|
||||
|
||||
/**
|
||||
* Renames `.git/gittally` to `.git/werkator` in [workingDir], if the first exists and
|
||||
* the second does not. Never throws: a failed move must not stop a CI, it must say
|
||||
* what to do by hand.
|
||||
*/
|
||||
fun migrateIfNeeded(workingDir: Path) {
|
||||
val legacy = workingDir.resolve(LEGACY_DIR)
|
||||
val current = workingDir.resolve(DIR)
|
||||
if (!Files.isDirectory(legacy)) return
|
||||
if (Files.exists(current)) {
|
||||
// both exist: which of the two is the live state is not ours to guess
|
||||
log.warn("{} exists next to {} — the leftover is ignored, remove it once you are sure", LEGACY_DIR, DIR)
|
||||
return
|
||||
}
|
||||
try {
|
||||
Files.move(legacy, current)
|
||||
} catch (e: IOException) {
|
||||
log.error("could not move {} to {}: {} — move it by hand", LEGACY_DIR, DIR, e.message)
|
||||
return
|
||||
}
|
||||
log.info("moved {} to {}: build history, control token and configuration kept", LEGACY_DIR, DIR)
|
||||
renameMachineConfig(current)
|
||||
dropWorktrees(current)
|
||||
warnAboutUnits(current)
|
||||
}
|
||||
|
||||
/**
|
||||
* Moving the directory leaves the file inside it under its old name, and the pair
|
||||
* of names is then one the lookup does not expect. Renaming it here is what makes
|
||||
* the move complete instead of half-done.
|
||||
*/
|
||||
private fun renameMachineConfig(stateDir: Path) {
|
||||
val legacy = stateDir.resolve(ConfigFiles.LEGACY_COMMITTED)
|
||||
val current = stateDir.resolve(ConfigFiles.COMMITTED)
|
||||
if (!Files.isRegularFile(legacy) || Files.exists(current)) return
|
||||
try {
|
||||
Files.move(legacy, current)
|
||||
} catch (e: IOException) {
|
||||
log.warn("could not rename {} to {} in {}: {}", ConfigFiles.LEGACY_COMMITTED, ConfigFiles.COMMITTED, stateDir, e.message)
|
||||
return
|
||||
}
|
||||
log.info("renamed the machine configuration to {}", ConfigFiles.COMMITTED)
|
||||
}
|
||||
|
||||
/**
|
||||
* The moved worktrees point at their old path in both directions, so they are dropped
|
||||
* rather than repaired: the next build of a branch creates its worktree again, and
|
||||
* `GitWorktreeWorkspaces` prunes the stale admin entry before it does.
|
||||
*/
|
||||
private fun dropWorktrees(stateDir: Path) {
|
||||
val worktrees = stateDir.resolve(WORKTREES)
|
||||
if (!Files.isDirectory(worktrees)) return
|
||||
if (worktrees.toFile().deleteRecursively()) {
|
||||
log.info("dropped the moved build worktrees; each is recreated by its branch's next build")
|
||||
} else {
|
||||
log.warn("could not drop the moved build worktrees in {} — delete them by hand", worktrees)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The generated systemd unit lives in the state directory and is symlinked from
|
||||
* `~/.config/systemd/user`, so the move leaves that link dangling — the service keeps
|
||||
* running and fails to start the next time.
|
||||
*/
|
||||
private fun warnAboutUnits(stateDir: Path) {
|
||||
val units =
|
||||
Files
|
||||
.list(stateDir)
|
||||
.use { paths -> paths.map { it.fileName.toString() }.filter { it.endsWith(".service") }.toList() }
|
||||
if (units.isEmpty()) return
|
||||
log.warn(
|
||||
"the systemd unit {} moved with the state directory and its symlink now dangles — " +
|
||||
"re-run `werkator init --systemd` and re-link it",
|
||||
units.joinToString(", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
+9
-6
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally
|
||||
package de.hoennig.werkator
|
||||
|
||||
import de.hoennig.gittally.config.ConfigException
|
||||
import de.hoennig.werkator.config.ConfigException
|
||||
import org.springframework.boot.CommandLineRunner
|
||||
import org.springframework.boot.ExitCodeGenerator
|
||||
import org.springframework.boot.SpringApplication
|
||||
@@ -10,26 +10,29 @@ import org.springframework.context.annotation.Profile
|
||||
import org.springframework.stereotype.Component
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.IFactory
|
||||
import java.nio.file.Paths
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@SpringBootApplication
|
||||
class GitTallyApplication
|
||||
class WerkatorApplication
|
||||
|
||||
/** Not in the `server` profile: the second context started by `ServerCommand` must not run picocli again. */
|
||||
@Component
|
||||
@Profile("!server")
|
||||
class CliRunner(
|
||||
private val factory: IFactory,
|
||||
private val rootCommand: GitTallyCommand,
|
||||
private val rootCommand: WerkatorCommand,
|
||||
) : CommandLineRunner,
|
||||
ExitCodeGenerator {
|
||||
private var exitCode = 0
|
||||
|
||||
override fun run(vararg args: String) {
|
||||
// before any command resolves a path under it, and once per process
|
||||
StateDirMigration.migrateIfNeeded(Paths.get("."))
|
||||
exitCode =
|
||||
CommandLine(rootCommand, factory)
|
||||
.setExecutionExceptionHandler { exception, commandLine, _ ->
|
||||
// a config GitTally must not read is a stated fact, not a crash: the message
|
||||
// a config Werkator must not read is a stated fact, not a crash: the message
|
||||
// names the file, the versions, and the way out — a stack trace would bury it
|
||||
if (exception is ConfigException) {
|
||||
commandLine.err.println("Error: ${exception.message}")
|
||||
@@ -49,5 +52,5 @@ class CliRunner(
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
exitProcess(SpringApplication.exit(runApplication<GitTallyApplication>(*args)))
|
||||
exitProcess(SpringApplication.exit(runApplication<WerkatorApplication>(*args)))
|
||||
}
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
package de.hoennig.gittally
|
||||
package de.hoennig.werkator
|
||||
|
||||
import de.hoennig.gittally.commands.BuildCommand
|
||||
import de.hoennig.gittally.commands.ConfigPrintCommand
|
||||
import de.hoennig.gittally.commands.InitCommand
|
||||
import de.hoennig.gittally.commands.RetryCommand
|
||||
import de.hoennig.gittally.commands.ServerCommand
|
||||
import de.hoennig.gittally.commands.StatusCommand
|
||||
import de.hoennig.werkator.commands.BuildCommand
|
||||
import de.hoennig.werkator.commands.ConfigPrintCommand
|
||||
import de.hoennig.werkator.commands.InitCommand
|
||||
import de.hoennig.werkator.commands.RetryCommand
|
||||
import de.hoennig.werkator.commands.ServerCommand
|
||||
import de.hoennig.werkator.commands.StatusCommand
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.boot.info.BuildProperties
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -14,7 +14,7 @@ import picocli.CommandLine.Command
|
||||
|
||||
@Component
|
||||
@Command(
|
||||
name = "gittally",
|
||||
name = "werkator",
|
||||
subcommands = [
|
||||
InitCommand::class,
|
||||
ServerCommand::class,
|
||||
@@ -27,7 +27,7 @@ import picocli.CommandLine.Command
|
||||
versionProvider = BuildPropertiesVersionProvider::class,
|
||||
description = ["Lightweight, declarative CI/CD system"],
|
||||
)
|
||||
class GitTallyCommand : Runnable {
|
||||
class WerkatorCommand : Runnable {
|
||||
override fun run(): Unit = throw CommandLine.ParameterException(CommandLine(this), "Specify a subcommand")
|
||||
}
|
||||
|
||||
@@ -41,5 +41,5 @@ class GitTallyCommand : Runnable {
|
||||
class BuildPropertiesVersionProvider(
|
||||
private val buildProperties: ObjectProvider<BuildProperties>,
|
||||
) : CommandLine.IVersionProvider {
|
||||
override fun getVersion(): Array<String> = arrayOf("GitTally v${buildProperties.getIfAvailable()?.version ?: "dev"}")
|
||||
override fun getVersion(): Array<String> = arrayOf("Werkator v${buildProperties.getIfAvailable()?.version ?: "dev"}")
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.artifacts
|
||||
package de.hoennig.werkator.artifacts
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
+10
-10
@@ -1,10 +1,10 @@
|
||||
package de.hoennig.gittally.artifacts
|
||||
package de.hoennig.werkator.artifacts
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactKeys
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.config.BranchConfig
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.werkator.build.ArtifactKeys
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.config.BranchConfig
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileVisitResult
|
||||
@@ -22,7 +22,7 @@ import kotlin.concurrent.write
|
||||
/**
|
||||
* Stores build artifacts on the filesystem under `<root>/branches/<artifactKey>/`.
|
||||
* The root is `artifacts.rootDir` from the config, or the platform default
|
||||
* `XDG_STATE_HOME` (falling back to `~/.local/state`) plus `/gittally/artifacts/<repo-key>` when unset —
|
||||
* `XDG_STATE_HOME` (falling back to `~/.local/state`) plus `/werkator/artifacts/<repo-key>` when unset —
|
||||
* deliberately not `/tmp` like legacy, where artifacts vanished on reboot.
|
||||
*
|
||||
* Each build is assembled in a temporary directory next to its target and moved
|
||||
@@ -115,7 +115,7 @@ class FileArtifactStore(
|
||||
env("XDG_STATE_HOME")?.takeIf { it.isNotBlank() }?.let { Paths.get(it) }
|
||||
?: Paths.get(System.getProperty("user.home"), ".local", "state")
|
||||
return stateHome
|
||||
.resolve("gittally")
|
||||
.resolve("werkator")
|
||||
.resolve("artifacts")
|
||||
.resolve(ArtifactKeys.repoKey(workingDir))
|
||||
.toAbsolutePath()
|
||||
@@ -160,9 +160,9 @@ class FileArtifactStore(
|
||||
}
|
||||
|
||||
/**
|
||||
* The settings [build] ran with, from the build [workspace]'s `.gittally.yml` layered
|
||||
* The settings [build] ran with, from the build [workspace]'s `.werkator.yml` layered
|
||||
* on top of the primary config (see [ConfigLoader.loadForWorktree]) — resolved through
|
||||
* [GitTallyConfig.buildSettings], so a job's own `artifactDirs` are archived and not
|
||||
* [WerkatorConfig.buildSettings], so a job's own `artifactDirs` are archived and not
|
||||
* only the ones its branch would have used.
|
||||
*/
|
||||
private fun buildSettings(
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.security.MessageDigest
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Persists the artifacts of finished builds (logs plus configured report directories)
|
||||
* and prunes them together with the result retention.
|
||||
* Implemented by `de.hoennig.gittally.artifacts.FileArtifactStore`.
|
||||
* Implemented by `de.hoennig.werkator.artifacts.FileArtifactStore`.
|
||||
*/
|
||||
interface ArtifactStore {
|
||||
/**
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Files
|
||||
@@ -20,7 +20,7 @@ fun interface BranchWorkspaces {
|
||||
}
|
||||
|
||||
/**
|
||||
* One reusable git worktree per branch under `.git/gittally/worktrees/<branchKey>`,
|
||||
* One reusable git worktree per branch under `.git/werkator/worktrees/<branchKey>`,
|
||||
* checked out detached at the requested commit. Reuse keeps incremental build
|
||||
* caches; the branch's `cleanCommand` decides how much of them survives.
|
||||
*/
|
||||
@@ -50,6 +50,6 @@ class GitWorktreeWorkspaces(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WORKTREES_DIR = ".git/gittally/worktrees"
|
||||
const val WORKTREES_DIR = ".git/werkator/worktrees"
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
@@ -8,9 +8,9 @@ import java.nio.file.Paths
|
||||
class BuildConfiguration {
|
||||
/**
|
||||
* Results file relative to the working directory, matching how `ConfigLoader`
|
||||
* resolves the `.git/gittally/` override file. Nothing is touched until the
|
||||
* resolves the `.git/werkator/` override file. Nothing is touched until the
|
||||
* first build runs, so the bean is safe outside a git repository.
|
||||
*/
|
||||
@Bean
|
||||
fun buildResultRepository(): BuildResultRepository = FileBuildResultRepository(Paths.get(".git/gittally/build-results.json"))
|
||||
fun buildResultRepository(): BuildResultRepository = FileBuildResultRepository(Paths.get(".git/werkator/build-results.json"))
|
||||
}
|
||||
+9
-9
@@ -1,9 +1,9 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.config.BranchConfig
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.gitea.GiteaClient
|
||||
import de.hoennig.werkator.config.BranchConfig
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import de.hoennig.werkator.gitea.GiteaClient
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.ApplicationEventPublisher
|
||||
import org.springframework.context.event.ContextClosedEvent
|
||||
@@ -93,7 +93,7 @@ class BuildExecutor(
|
||||
return duplicate.runningBuild
|
||||
}
|
||||
val startedAt = Instant.now()
|
||||
val stagingDir = Files.createTempDirectory("gittally-build-")
|
||||
val stagingDir = Files.createTempDirectory("werkator-build-")
|
||||
val runningBuild =
|
||||
RunningBuild(
|
||||
branch = branch,
|
||||
@@ -249,7 +249,7 @@ class BuildExecutor(
|
||||
|
||||
private fun serialWorker(branch: String): ExecutorService =
|
||||
Executors.newSingleThreadExecutor { runnable ->
|
||||
Thread(runnable, "gittally-build-${ArtifactKeys.branchKey(branch)}").apply { isDaemon = true }
|
||||
Thread(runnable, "werkator-build-${ArtifactKeys.branchKey(branch)}").apply { isDaemon = true }
|
||||
}
|
||||
|
||||
private fun runBuildCommands(
|
||||
@@ -324,7 +324,7 @@ class BuildExecutor(
|
||||
input: InputStream,
|
||||
vararg sinks: OutputStream,
|
||||
): Thread =
|
||||
thread(isDaemon = true, name = "gittally-build-log") {
|
||||
thread(isDaemon = true, name = "werkator-build-log") {
|
||||
val buffer = ByteArray(8192)
|
||||
try {
|
||||
while (true) {
|
||||
@@ -480,7 +480,7 @@ class BuildExecutor(
|
||||
|
||||
/**
|
||||
* The effective settings of this run: the branch config with the build [worktree]'s
|
||||
* `.gittally.yml` layered on top (see [ConfigLoader.loadForWorktree]), then the
|
||||
* `.werkator.yml` layered on top (see [ConfigLoader.loadForWorktree]), then the
|
||||
* build definition's overrides applied last — the job wins, and it always comes
|
||||
* from the primary config (`builds` is a pinned section). An unknown build name
|
||||
* (a stale result whose job was removed) falls back to the plain branch settings.
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.config.BranchConfig
|
||||
import de.hoennig.werkator.config.BranchConfig
|
||||
import org.springframework.context.annotation.Primary
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Path
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
enum class BuildStatus {
|
||||
PENDING,
|
||||
+23
-23
@@ -1,8 +1,8 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.config.BranchConfig
|
||||
import de.hoennig.gittally.config.DockerConfig
|
||||
import de.hoennig.gittally.git.GitCommandRunner
|
||||
import de.hoennig.werkator.config.BranchConfig
|
||||
import de.hoennig.werkator.config.DockerConfig
|
||||
import de.hoennig.werkator.git.GitCommandRunner
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Files
|
||||
@@ -23,7 +23,7 @@ import java.nio.file.Path
|
||||
* same container run; under a rootless daemon the container runs as root, which
|
||||
* already is the host user, so the repair chown degenerates to `0:0`.
|
||||
* Git works inside the container: the primary `.git` is mounted read-only with
|
||||
* `.git/gittally/` masked, see [gitMetadataMounts].
|
||||
* `.git/werkator/` masked, see [gitMetadataMounts].
|
||||
*/
|
||||
@Component
|
||||
class DockerBuildRunner(
|
||||
@@ -114,11 +114,11 @@ class DockerBuildRunner(
|
||||
"docker",
|
||||
"build",
|
||||
"--label",
|
||||
"org.gittally.dockerfile=${docker.dockerfile}",
|
||||
"org.werkator.dockerfile=${docker.dockerfile}",
|
||||
"--label",
|
||||
"org.gittally.dockerfile-sha256=$dockerfileHash",
|
||||
"org.werkator.dockerfile-sha256=$dockerfileHash",
|
||||
"--label",
|
||||
"org.gittally.build-context=${docker.context}",
|
||||
"org.werkator.build-context=${docker.context}",
|
||||
"--label",
|
||||
"${DockerImageInputs.INPUTS_LABEL}=$inputsHash",
|
||||
"-t",
|
||||
@@ -194,11 +194,11 @@ class DockerBuildRunner(
|
||||
"ps",
|
||||
"-aq",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL=true",
|
||||
"label=$WERKATOR_LABEL=true",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL.repository=$repoKey",
|
||||
"label=$WERKATOR_LABEL.repository=$repoKey",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL.role=build",
|
||||
"label=$WERKATOR_LABEL.role=build",
|
||||
),
|
||||
repoDir,
|
||||
)
|
||||
@@ -238,11 +238,11 @@ class DockerBuildRunner(
|
||||
args +=
|
||||
listOf(
|
||||
"--label",
|
||||
"$GITTALLY_LABEL=true",
|
||||
"$WERKATOR_LABEL=true",
|
||||
"--label",
|
||||
"$GITTALLY_LABEL.repository=$repoKey",
|
||||
"$WERKATOR_LABEL.repository=$repoKey",
|
||||
"--label",
|
||||
"$GITTALLY_LABEL.role=build",
|
||||
"$WERKATOR_LABEL.role=build",
|
||||
)
|
||||
args += listOf("--workdir", "$workspace", "--volume", "$workspace:$workspace")
|
||||
args += gitMetadataMounts(workspace, repoDir)
|
||||
@@ -279,12 +279,12 @@ class DockerBuildRunner(
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes git work inside the build container without exposing GitTally's secrets.
|
||||
* Makes git work inside the build container without exposing Werkator's secrets.
|
||||
*
|
||||
* The workspace is a git worktree whose `.git` file points into the primary
|
||||
* repository's `.git`, which is not part of the workspace mount — so any git call
|
||||
* in the build would fail. Three layered mounts fix that (Docker nests mounts by
|
||||
* target path): the primary `.git` read-only, an empty tmpfs masking `.git/gittally/`
|
||||
* target path): the primary `.git` read-only, an empty tmpfs masking `.git/werkator/`
|
||||
* (machine config with `git.token`, control token, build state — the workspace bind
|
||||
* resurfaces only this build's own worktree inside it), and this worktree's admin
|
||||
* directory read-write, so index-refreshing commands like `git status` keep working.
|
||||
@@ -312,9 +312,9 @@ class DockerBuildRunner(
|
||||
return emptyList()
|
||||
}
|
||||
val args = mutableListOf("--volume", "$gitDir:$gitDir:ro")
|
||||
val gittallyDir = gitDir.resolve("gittally")
|
||||
if (Files.isDirectory(gittallyDir)) {
|
||||
args += listOf("--tmpfs", "$gittallyDir")
|
||||
val werkatorDir = gitDir.resolve("werkator")
|
||||
if (Files.isDirectory(werkatorDir)) {
|
||||
args += listOf("--tmpfs", "$werkatorDir")
|
||||
}
|
||||
args += listOf("--volume", "$adminDir:$adminDir")
|
||||
return args
|
||||
@@ -326,15 +326,15 @@ class DockerBuildRunner(
|
||||
): String = commandRunner.runOrThrow(listOf("id", flag), repoDir).stdout.trim()
|
||||
|
||||
companion object {
|
||||
/** Container label namespace; legacy used `org.hostsharing.gittally`. */
|
||||
const val GITTALLY_LABEL = "org.hoennig.gittally"
|
||||
/** Container label namespace; legacy used `org.hostsharing.werkator`. */
|
||||
const val WERKATOR_LABEL = "org.hoennig.werkator"
|
||||
|
||||
fun gradleVolumeName(repoKey: String): String = "gittally-gradle-$repoKey"
|
||||
fun gradleVolumeName(repoKey: String): String = "werkator-gradle-$repoKey"
|
||||
|
||||
fun containerName(
|
||||
repoKey: String,
|
||||
branch: String?,
|
||||
): String = "gittally-build-$repoKey" + (branch?.let { "-${ArtifactKeys.branchKey(it)}" } ?: "")
|
||||
): String = "werkator-build-$repoKey" + (branch?.let { "-${ArtifactKeys.branchKey(it)}" } ?: "")
|
||||
|
||||
private val INSPECT_INPUTS_LABEL_FORMAT = """{{ index .Config.Labels "${DockerImageInputs.INPUTS_LABEL}" }}"""
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
@@ -10,7 +10,7 @@ import java.security.MessageDigest
|
||||
* with the configured Dockerfile and context paths, stored as an image label.
|
||||
*/
|
||||
object DockerImageInputs {
|
||||
const val INPUTS_LABEL = "org.gittally.build-inputs-sha256"
|
||||
const val INPUTS_LABEL = "org.werkator.build-inputs-sha256"
|
||||
|
||||
fun dockerfileSha256(dockerfile: Path): String = sha256Hex(Files.readAllBytes(dockerfile))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Files
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
@@ -13,7 +13,7 @@ import java.nio.file.StandardCopyOption
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Stores build results as a JSON file, e.g. `.git/gittally/build-results.json`.
|
||||
* Stores build results as a JSON file, e.g. `.git/werkator/build-results.json`.
|
||||
* Writes are atomic (temp file + atomic move) so readers never see partial content.
|
||||
*/
|
||||
class FileBuildResultRepository(
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.build
|
||||
package de.hoennig.werkator.build
|
||||
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
/**
|
||||
* Port of the legacy `resolve_branch_name` partial-name matching: a branch-name
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.springframework.stereotype.Component
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.ExitCode
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import org.springframework.stereotype.Component
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.Option
|
||||
+9
-9
@@ -1,13 +1,13 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.build.BuildExecutor
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.build.RunningBuild
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.gittally.server.UiFormats
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.BuildExecutor
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.build.RunningBuild
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import de.hoennig.werkator.server.UiFormats
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.IOException
|
||||
import java.nio.channels.Channels
|
||||
+36
-30
@@ -1,7 +1,8 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.SecretFiles
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.SecretFiles
|
||||
import de.hoennig.werkator.config.ConfigFiles
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.boot.info.BuildProperties
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -13,19 +14,19 @@ import java.nio.file.Paths
|
||||
@Component
|
||||
@Command(
|
||||
name = "init",
|
||||
description = ["Initialize GitTally for the current repository"],
|
||||
description = ["Initialize Werkator for the current repository"],
|
||||
mixinStandardHelpOptions = true,
|
||||
)
|
||||
class InitCommand(
|
||||
private val gitService: GitService,
|
||||
/** The version written into the generated config as `gitTally.version.since`. */
|
||||
/** The version written into the generated config as `werkator.version.since`. */
|
||||
private val buildProperties: ObjectProvider<BuildProperties>? = null,
|
||||
) : Runnable {
|
||||
var workingDir: Path = Paths.get(".")
|
||||
|
||||
@Option(
|
||||
names = ["--systemd"],
|
||||
description = ["also generate a systemd user unit that runs `gittally server` for this repository"],
|
||||
description = ["also generate a systemd user unit that runs `werkator server` for this repository"],
|
||||
)
|
||||
var systemd: Boolean = false
|
||||
|
||||
@@ -56,7 +57,7 @@ class InitCommand(
|
||||
}
|
||||
|
||||
/**
|
||||
* The running version for `gitTally.version.since`; outside a built jar (IDE, tests)
|
||||
* The running version for `werkator.version.since`; outside a built jar (IDE, tests)
|
||||
* there is none, and `0.0.0` then declares no floor at all rather than a wrong one.
|
||||
*/
|
||||
private fun runningVersion(): String = buildProperties?.getIfAvailable()?.version ?: "0.0.0"
|
||||
@@ -99,7 +100,8 @@ class InitCommand(
|
||||
detected: DetectedValues,
|
||||
normalizedWorkingDir: Path,
|
||||
) {
|
||||
val file = root.resolve(".git/gittally/.gittally.yml")
|
||||
val existing = ConfigFiles.firstExisting(root, ConfigFiles.repoInstall)
|
||||
val file = root.resolve(existing)
|
||||
if (file.toFile().exists()) {
|
||||
println("${file.toFile().relativeTo(normalizedWorkingDir.toFile())} already exists — not overwritten")
|
||||
return
|
||||
@@ -107,7 +109,7 @@ class InitCommand(
|
||||
SecretFiles.createDirectoriesOwnerOnly(file.parent)
|
||||
val content =
|
||||
"""
|
||||
# Machine- or user-specific overrides and secrets. Keys here win over .gittally.yml.
|
||||
# Machine- or user-specific overrides and secrets. Keys here win over .werkator.yml.
|
||||
git:
|
||||
account: "${detected.account}" # technical username for git HTTPS authentication
|
||||
token: "" # Gitea API token — never commit this
|
||||
@@ -123,31 +125,35 @@ class InitCommand(
|
||||
detected: DetectedValues,
|
||||
normalizedWorkingDir: Path,
|
||||
) {
|
||||
val file = root.resolve(".gittally.yml")
|
||||
// under either name: writing a second one beside a config under the previous
|
||||
// name would shadow it, and a repository would silently start building
|
||||
// something else than what it says it builds
|
||||
val existing = ConfigFiles.firstExisting(root)
|
||||
val file = root.resolve(existing)
|
||||
if (file.toFile().exists()) {
|
||||
println("${file.toFile().relativeTo(normalizedWorkingDir.toFile())} already exists — not overwritten")
|
||||
return
|
||||
}
|
||||
val content =
|
||||
"""
|
||||
# The GitTally this file is written for.
|
||||
# since: enforced — an older GitTally refuses to read this file instead of
|
||||
# The Werkator this file is written for.
|
||||
# since: enforced — an older Werkator refuses to read this file instead of
|
||||
# silently ignoring the keys it does not know yet.
|
||||
# below: your release marker for a coming major; GitTally decides how strictly
|
||||
# below: your release marker for a coming major; Werkator decides how strictly
|
||||
# to take it, and warns rather than blocks unless the format really broke.
|
||||
gitTally:
|
||||
werkator:
|
||||
version:
|
||||
since: "${runningVersion()}"
|
||||
# below: "2.0"
|
||||
|
||||
server:
|
||||
# Public base URL of this GitTally installation — used for all links posted to Gitea.
|
||||
# Public base URL of this Werkator installation — used for all links posted to Gitea.
|
||||
publicBaseUrl: ""
|
||||
# HTTP port of the `server` subcommand
|
||||
port: 18080
|
||||
# bind address of the `server` subcommand; loopback only, because the UI and the
|
||||
# API are unauthenticated — use 0.0.0.0 only without a reverse proxy in front
|
||||
# (and with the managed nginx below, which reaches GitTally from its container)
|
||||
# (and with the managed nginx below, which reaches Werkator from its container)
|
||||
bindAddress: 127.0.0.1
|
||||
# optional Impressum (legal disclosure) link in the web UI footer; empty hides the link
|
||||
impressumUrl: ""
|
||||
@@ -159,8 +165,8 @@ class InitCommand(
|
||||
httpPort: 8080 # host port published as nginx port 80
|
||||
httpsPort: 8443 # host port published as nginx port 443
|
||||
upstreamHost: "" # host nginx proxies to; empty = serverName
|
||||
containerName: "" # empty = gittally-nginx-<repo-name>
|
||||
stateDir: "" # empty = XDG_STATE_HOME (or ~/.local/state) + /gittally/nginx/<repo-key>
|
||||
containerName: "" # empty = werkator-nginx-<repo-name>
|
||||
stateDir: "" # empty = XDG_STATE_HOME (or ~/.local/state) + /werkator/nginx/<repo-key>
|
||||
letsencryptEmail: "" # e-mail for the Let's Encrypt account; empty registers without one
|
||||
|
||||
# Gitea integration for fetching commits and posting build statuses.
|
||||
@@ -168,7 +174,7 @@ class InitCommand(
|
||||
baseUrl: ${detected.baseUrl} # base URL of the Gitea instance
|
||||
owner: ${detected.owner} # repository owner (user or organisation) for Gitea API (e.g. status checks)
|
||||
repo: ${detected.repo} # repository name
|
||||
statusContext: GitTally # label shown on Gitea commit status checks (default: GitTally)
|
||||
statusContext: werkator # label shown on Gitea commit status checks (default: werkator)
|
||||
|
||||
# Build execution settings, enforced for all builds regardless of their trigger.
|
||||
executor:
|
||||
@@ -178,7 +184,7 @@ class InitCommand(
|
||||
# Named build definitions (jobs); every key names a build.
|
||||
# "default" is the base every other definition inherits its settings from — never
|
||||
# its trigger — and is itself the build of every branch as long as it has one.
|
||||
# A branch may add or override definitions in its own committed .gittally.yml;
|
||||
# A branch may add or override definitions in its own committed .werkator.yml;
|
||||
# they apply to that branch alone, so a new job can be tried out on one branch.
|
||||
builds:
|
||||
default:
|
||||
@@ -218,11 +224,11 @@ class InitCommand(
|
||||
# atTimes: ["01:00"]
|
||||
# branches: ["master"]
|
||||
# buildCommand: ./gradlew pitestFull
|
||||
# statusContext: GitTally/pitest
|
||||
# statusContext: werkator/pitest
|
||||
|
||||
# Build artifact storage and retention.
|
||||
artifacts:
|
||||
# root directory for stored artifacts; empty = XDG_STATE_HOME (or ~/.local/state) + /gittally/artifacts/<repo-key>
|
||||
# root directory for stored artifacts; empty = XDG_STATE_HOME (or ~/.local/state) + /werkator/artifacts/<repo-key>
|
||||
rootDir: ""
|
||||
# number of builds to keep per branch
|
||||
retentionPerBranch: 3
|
||||
@@ -256,14 +262,14 @@ class InitCommand(
|
||||
) {
|
||||
val jarPath = jarPathResolver()
|
||||
if (jarPath == null) {
|
||||
println("Error: cannot determine the GitTally jar path — run `init --systemd` via `java -jar <path-to>/gittally.jar`")
|
||||
println("Error: cannot determine the Werkator jar path — run `init --systemd` via `java -jar <path-to>/werkator.jar`")
|
||||
return
|
||||
}
|
||||
val gittallyDir = root.resolve(".git/gittally")
|
||||
SecretFiles.createDirectoriesOwnerOnly(gittallyDir)
|
||||
val werkatorDir = root.resolve(".git/werkator")
|
||||
SecretFiles.createDirectoriesOwnerOnly(werkatorDir)
|
||||
val unitName = SystemdServiceFiles.unitName(root)
|
||||
val unitFile = gittallyDir.resolve(unitName)
|
||||
val envFile = gittallyDir.resolve(SystemdServiceFiles.ENV_FILE_NAME)
|
||||
val unitFile = werkatorDir.resolve(unitName)
|
||||
val envFile = werkatorDir.resolve(SystemdServiceFiles.ENV_FILE_NAME)
|
||||
|
||||
unitFile.toFile().writeText(
|
||||
SystemdServiceFiles.unitFileContent(
|
||||
@@ -283,9 +289,9 @@ class InitCommand(
|
||||
}
|
||||
|
||||
// the nightly Docker cleanup is host-global: every repository generates the same
|
||||
// units, so with several GitTally instances the symlinks simply coincide
|
||||
val pruneServiceFile = gittallyDir.resolve(SystemdServiceFiles.PRUNE_SERVICE_NAME)
|
||||
val pruneTimerFile = gittallyDir.resolve(SystemdServiceFiles.PRUNE_TIMER_NAME)
|
||||
// units, so with several Werkator instances the symlinks simply coincide
|
||||
val pruneServiceFile = werkatorDir.resolve(SystemdServiceFiles.PRUNE_SERVICE_NAME)
|
||||
val pruneTimerFile = werkatorDir.resolve(SystemdServiceFiles.PRUNE_TIMER_NAME)
|
||||
pruneServiceFile.toFile().writeText(SystemdServiceFiles.pruneServiceContent())
|
||||
println("created ${pruneServiceFile.toFile().relativeTo(normalizedWorkingDir.toFile())}")
|
||||
pruneTimerFile.toFile().writeText(SystemdServiceFiles.pruneTimerContent())
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.springframework.stereotype.Component
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.ExitCode
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.GitTallyApplication
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.werkator.WerkatorApplication
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import org.springframework.boot.WebApplicationType
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder
|
||||
import org.springframework.context.ApplicationListener
|
||||
@@ -24,7 +24,7 @@ import java.util.concurrent.CountDownLatch
|
||||
@Component
|
||||
@Command(
|
||||
name = "server",
|
||||
description = ["Start the GitTally server"],
|
||||
description = ["Start the Werkator server"],
|
||||
mixinStandardHelpOptions = true,
|
||||
)
|
||||
class ServerCommand(
|
||||
@@ -35,7 +35,7 @@ class ServerCommand(
|
||||
override fun run() {
|
||||
val config = configLoader.load(workingDir)
|
||||
val context =
|
||||
SpringApplicationBuilder(GitTallyApplication::class.java)
|
||||
SpringApplicationBuilder(WerkatorApplication::class.java)
|
||||
.web(WebApplicationType.SERVLET)
|
||||
.profiles(SERVER_PROFILE)
|
||||
.properties(
|
||||
@@ -43,7 +43,7 @@ class ServerCommand(
|
||||
"server.address=${config.server.bindAddress}",
|
||||
).run()
|
||||
val port = context.environment.getProperty("local.server.port", config.server.port.toString())
|
||||
println("GitTally server listening on http://${config.server.bindAddress}:$port/ — Ctrl-C to stop")
|
||||
println("Werkator server listening on http://${config.server.bindAddress}:$port/ — Ctrl-C to stop")
|
||||
awaitShutdown(context)
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.server.UiFormats
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.server.UiFormats
|
||||
import org.springframework.stereotype.Component
|
||||
import picocli.CommandLine.Command
|
||||
import picocli.CommandLine.ExitCode
|
||||
+13
-13
@@ -1,21 +1,21 @@
|
||||
package de.hoennig.gittally.commands
|
||||
package de.hoennig.werkator.commands
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Generates the content of the systemd user unit and its `EnvironmentFile` for running
|
||||
* `gittally server` as a service — the shape of the legacy `generate_systemd_config`,
|
||||
* `werkator server` as a service — the shape of the legacy `generate_systemd_config`,
|
||||
* without the self-copy/self-update machinery (the unit points at the jar in place).
|
||||
*/
|
||||
object SystemdServiceFiles {
|
||||
const val ENV_FILE_NAME = "gittally.env"
|
||||
const val ENV_FILE_NAME = "werkator.env"
|
||||
|
||||
/** Host-global unit names of the nightly Docker cleanup — shared by all GitTally repositories on the host. */
|
||||
const val PRUNE_SERVICE_NAME = "gittally-docker-prune.service"
|
||||
const val PRUNE_TIMER_NAME = "gittally-docker-prune.timer"
|
||||
/** Host-global unit names of the nightly Docker cleanup — shared by all Werkator repositories on the host. */
|
||||
const val PRUNE_SERVICE_NAME = "werkator-docker-prune.service"
|
||||
const val PRUNE_TIMER_NAME = "werkator-docker-prune.timer"
|
||||
|
||||
/** Per-repository unit name, because one GitTally instance serves exactly one repository. */
|
||||
fun unitName(repoRoot: Path): String = "gittally-${sanitize(repoRoot.fileName.toString())}.service"
|
||||
/** Per-repository unit name, because one Werkator instance serves exactly one repository. */
|
||||
fun unitName(repoRoot: Path): String = "werkator-${sanitize(repoRoot.fileName.toString())}.service"
|
||||
|
||||
fun unitFileContent(
|
||||
repoRoot: Path,
|
||||
@@ -25,7 +25,7 @@ object SystemdServiceFiles {
|
||||
): String =
|
||||
"""
|
||||
[Unit]
|
||||
Description=GitTally CI for ${repoRoot.fileName}
|
||||
Description=Werkator CI for ${repoRoot.fileName}
|
||||
Wants=network-online.target
|
||||
After=network-online.target docker.service
|
||||
|
||||
@@ -49,7 +49,7 @@ object SystemdServiceFiles {
|
||||
fun pruneServiceContent(): String =
|
||||
"""
|
||||
[Unit]
|
||||
Description=Clean up unused Docker containers and images (GitTally)
|
||||
Description=Clean up unused Docker containers and images (Werkator)
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
@@ -62,7 +62,7 @@ object SystemdServiceFiles {
|
||||
fun pruneTimerContent(): String =
|
||||
"""
|
||||
[Unit]
|
||||
Description=Nightly Docker cleanup before the auto builds (GitTally)
|
||||
Description=Nightly Docker cleanup before the auto builds (Werkator)
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 02:00:00
|
||||
@@ -74,8 +74,8 @@ object SystemdServiceFiles {
|
||||
|
||||
fun envFileContent(): String =
|
||||
"""
|
||||
# EnvironmentFile for the GitTally systemd service.
|
||||
# GitTally itself is configured via .gittally.yml and .git/gittally/.gittally.yml,
|
||||
# EnvironmentFile for the Werkator systemd service.
|
||||
# Werkator itself is configured via .werkator.yml and .git/werkator/.werkator.yml,
|
||||
# not via environment variables; this file only tunes the JVM process.
|
||||
#JAVA_OPTS=-Xmx256m
|
||||
""".trimIndent() + "\n"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.config
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* The names a configuration file is looked up under, current name first and the name
|
||||
* from before the rename to Werkator second — spelled exactly as it was.
|
||||
*
|
||||
* The fallback exists because a missing configuration is not an error: it leaves every
|
||||
* setting at its default. An installation that updates without moving its files would
|
||||
* therefore not fail, it would come up as a plausible-looking instance that has
|
||||
* forgotten its credentials, its addresses, and what it builds.
|
||||
*/
|
||||
object ConfigFiles {
|
||||
/** The committed configuration, at the repository root and in a build worktree. */
|
||||
const val COMMITTED = ".werkator.yml"
|
||||
|
||||
/** The machine-specific configuration inside `.git`; secrets live here. */
|
||||
const val REPO_INSTALL = ".git/werkator/$COMMITTED"
|
||||
|
||||
/** The name the committed configuration had before the rename. */
|
||||
const val LEGACY_COMMITTED = ".gittally.yml"
|
||||
|
||||
private const val LEGACY_REPO_INSTALL = ".git/gittally/$LEGACY_COMMITTED"
|
||||
|
||||
/**
|
||||
* The state directory moves without touching the file inside it, so between the
|
||||
* move and the rename the machine configuration sits under the old name in the
|
||||
* new directory. `StateDirMigration` closes that gap where it moves the directory
|
||||
* itself; this candidate covers a directory somebody moved by hand.
|
||||
*/
|
||||
private const val MOVED_REPO_INSTALL = ".git/werkator/$LEGACY_COMMITTED"
|
||||
|
||||
/** Both names of the committed configuration, current first. */
|
||||
val committed = listOf(COMMITTED, LEGACY_COMMITTED)
|
||||
|
||||
/** Every path the machine-specific configuration can sit at, current first. */
|
||||
val repoInstall = listOf(REPO_INSTALL, MOVED_REPO_INSTALL, LEGACY_REPO_INSTALL)
|
||||
|
||||
/**
|
||||
* The first of [candidates] that exists under [dir], or the current name when none
|
||||
* does — so a message about a file names the one to write, never the one that is
|
||||
* history.
|
||||
*/
|
||||
fun firstExisting(
|
||||
dir: Path,
|
||||
candidates: List<String> = committed,
|
||||
): String = candidates.firstOrNull { Files.isRegularFile(dir.resolve(it)) } ?: candidates.first()
|
||||
|
||||
/**
|
||||
* The committed configuration as [read] answers it for a name, current name first.
|
||||
* Null when neither name is committed — used where the file is read out of git
|
||||
* rather than off the filesystem.
|
||||
*/
|
||||
fun readCommitted(read: (String) -> String?): String? = committed.firstNotNullOfOrNull(read)
|
||||
}
|
||||
+28
-25
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.config
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
@@ -17,7 +17,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Service
|
||||
class ConfigLoader(
|
||||
/** The running version, for the `gitTally.version` check; absent outside a built jar (IDE, tests). */
|
||||
/** The running version, for the `werkator.version` check; absent outside a built jar (IDE, tests). */
|
||||
private val buildProperties: ObjectProvider<BuildProperties>? = null,
|
||||
) {
|
||||
private val log = LoggerFactory.getLogger(ConfigLoader::class.java)
|
||||
@@ -37,21 +37,21 @@ class ConfigLoader(
|
||||
/** Section-level warnings already reported, keyed by a fixed slug; the config is loaded on every poll cycle. */
|
||||
private val warnedSections = ConcurrentHashMap.newKeySet<String>()
|
||||
|
||||
fun load(workingDir: Path = Paths.get(".")): GitTallyConfig = toConfig(loadRaw(workingDir))
|
||||
fun load(workingDir: Path = Paths.get(".")): WerkatorConfig = toConfig(loadRaw(workingDir))
|
||||
|
||||
/**
|
||||
* Config for building a branch in [worktreeDir]: the worktree's `.gittally.yml`
|
||||
* Config for building a branch in [worktreeDir]: the worktree's `.werkator.yml`
|
||||
* (the committed config of the branch being built) is applied as the branch layer,
|
||||
* see [loadWithBranchLayer]. With no worktree `.gittally.yml` this is identical
|
||||
* see [loadWithBranchLayer]. With no worktree `.werkator.yml` this is identical
|
||||
* to [load].
|
||||
*/
|
||||
fun loadForWorktree(
|
||||
workingDir: Path,
|
||||
worktreeDir: Path,
|
||||
): GitTallyConfig = withBranchLayer(workingDir, loadFile(worktreeDir.resolve(".gittally.yml").toFile()))
|
||||
): WerkatorConfig = withBranchLayer(workingDir, loadFile(worktreeDir.resolve(ConfigFiles.firstExisting(worktreeDir)).toFile()))
|
||||
|
||||
/**
|
||||
* The primary/`.git` config with the committed `.gittally.yml` of one branch
|
||||
* The primary/`.git` config with the committed `.werkator.yml` of one branch
|
||||
* ([branchConfigYaml], null or blank for a branch without one) merged on top:
|
||||
* precedence branch > `.git` > project. A branch describes its own CI — build
|
||||
* settings (`buildCommand`, `cleanCommand`, `artifactDirs`, `docker.image`/`env`, …)
|
||||
@@ -70,25 +70,25 @@ class ConfigLoader(
|
||||
fun loadWithBranchLayer(
|
||||
workingDir: Path,
|
||||
branchConfigYaml: String?,
|
||||
): GitTallyConfig = withBranchLayer(workingDir, parseYaml(branchConfigYaml))
|
||||
): WerkatorConfig = withBranchLayer(workingDir, parseYaml(branchConfigYaml))
|
||||
|
||||
private fun withBranchLayer(
|
||||
workingDir: Path,
|
||||
branchLayer: Map<String, Any?>,
|
||||
): GitTallyConfig {
|
||||
): WerkatorConfig {
|
||||
// scoped to this branch: an incompatible branch config fails its own builds and
|
||||
// must never stop the server or hold up the branches that are fine
|
||||
checkVersion(branchLayer, "the committed .gittally.yml of this branch", BRANCH_HINT)
|
||||
checkTriggerBlocks(branchLayer, "the committed .gittally.yml of this branch", BRANCH_HINT)
|
||||
checkVersion(branchLayer, "the committed .werkator.yml of this branch", BRANCH_HINT)
|
||||
checkTriggerBlocks(branchLayer, "the committed .werkator.yml of this branch", BRANCH_HINT)
|
||||
return toConfig(deepMerge(loadRaw(workingDir), stripPinned(branchLayer)))
|
||||
}
|
||||
|
||||
private fun toConfig(raw: Map<String, Any?>): GitTallyConfig {
|
||||
private fun toConfig(raw: Map<String, Any?>): WerkatorConfig {
|
||||
val config =
|
||||
if (raw.isEmpty()) {
|
||||
GitTallyConfig()
|
||||
WerkatorConfig()
|
||||
} else {
|
||||
yaml.convertValue(resolveBuildSections(dropNonDefinitionBuilds(raw)), GitTallyConfig::class.java)
|
||||
yaml.convertValue(resolveBuildSections(dropNonDefinitionBuilds(raw)), WerkatorConfig::class.java)
|
||||
}
|
||||
return defaultPublicBaseUrl(config)
|
||||
}
|
||||
@@ -265,7 +265,7 @@ class ConfigLoader(
|
||||
}
|
||||
|
||||
/** Legacy default: an empty `server.publicBaseUrl` becomes `https://<nginx.serverName>/`. */
|
||||
private fun defaultPublicBaseUrl(config: GitTallyConfig): GitTallyConfig {
|
||||
private fun defaultPublicBaseUrl(config: WerkatorConfig): WerkatorConfig {
|
||||
if (config.server.publicBaseUrl.isNotBlank() ||
|
||||
config.server.nginx.serverName
|
||||
.isBlank()
|
||||
@@ -276,18 +276,21 @@ class ConfigLoader(
|
||||
}
|
||||
|
||||
fun loadRaw(workingDir: Path = Paths.get(".")): Map<String, Any?> {
|
||||
val repoInstall = loadFile(workingDir.resolve(".git/gittally/.gittally.yml").toFile())
|
||||
val project = loadFile(workingDir.resolve(".gittally.yml").toFile())
|
||||
// each layer under its current name, or under the one it had before the rename
|
||||
val repoInstallName = ConfigFiles.firstExisting(workingDir, ConfigFiles.repoInstall)
|
||||
val projectName = ConfigFiles.firstExisting(workingDir)
|
||||
val repoInstall = loadFile(workingDir.resolve(repoInstallName).toFile())
|
||||
val project = loadFile(workingDir.resolve(projectName).toFile())
|
||||
// per file, so the message names the file to fix — the merged map has no provenance
|
||||
checkVersion(project, ".gittally.yml", ROLLBACK_HINT)
|
||||
checkVersion(repoInstall, ".git/gittally/.gittally.yml", ROLLBACK_HINT)
|
||||
checkTriggerBlocks(project, ".gittally.yml", ROLLBACK_HINT)
|
||||
checkTriggerBlocks(repoInstall, ".git/gittally/.gittally.yml", ROLLBACK_HINT)
|
||||
checkVersion(project, projectName, ROLLBACK_HINT)
|
||||
checkVersion(repoInstall, repoInstallName, ROLLBACK_HINT)
|
||||
checkTriggerBlocks(project, projectName, ROLLBACK_HINT)
|
||||
checkTriggerBlocks(repoInstall, repoInstallName, ROLLBACK_HINT)
|
||||
return deepMerge(project, repoInstall)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the `gitTally.version` declaration of one configuration file.
|
||||
* Enforces the `werkator.version` declaration of one configuration file.
|
||||
* An incompatible file throws — reading it would mean honoring keys that mean
|
||||
* something else now, which is worse than not building. A file that merely exceeds
|
||||
* its own `below` marker is a warning, logged once: an unmaintained marker must
|
||||
@@ -314,7 +317,7 @@ class ConfigLoader(
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun requirementOf(raw: Map<String, Any?>): VersionRequirement {
|
||||
val version = (raw["gitTally"] as? Map<String, Any?>)?.get("version") as? Map<String, Any?> ?: return VersionRequirement()
|
||||
val version = (raw["werkator"] as? Map<String, Any?>)?.get("version") as? Map<String, Any?> ?: return VersionRequirement()
|
||||
return VersionRequirement(
|
||||
since = version["since"]?.toString()?.trim().orEmpty(),
|
||||
below = version["below"]?.toString()?.trim().orEmpty(),
|
||||
@@ -329,7 +332,7 @@ class ConfigLoader(
|
||||
return yaml.readValue(file, Map::class.java) as Map<String, Any?>
|
||||
}
|
||||
|
||||
/** Parses a `.gittally.yml` read from git (not from disk); blank or null yields no layer. */
|
||||
/** Parses a `.werkator.yml` read from git (not from disk); blank or null yields no layer. */
|
||||
private fun parseYaml(text: String?): Map<String, Any?> {
|
||||
if (text.isNullOrBlank()) return emptyMap()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -411,7 +414,7 @@ class ConfigLoader(
|
||||
private const val NO_TRIGGER_WARNING = "no-build-triggered"
|
||||
|
||||
private const val ROLLBACK_HINT =
|
||||
"Migrate the file, or roll back to the GitTally version it was written for."
|
||||
"Migrate the file, or roll back to the Werkator version it was written for."
|
||||
|
||||
private const val BRANCH_HINT =
|
||||
"Migrate the file on this branch; the other branches keep building."
|
||||
+20
-20
@@ -1,33 +1,33 @@
|
||||
package de.hoennig.gittally.config
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
/**
|
||||
* The GitTally version a configuration file declares itself for, the `gitTally.version`
|
||||
* The Werkator version a configuration file declares itself for, the `werkator.version`
|
||||
* section:
|
||||
*
|
||||
* ```yaml
|
||||
* gitTally:
|
||||
* werkator:
|
||||
* version:
|
||||
* since: "0.9.16" # always hard: an older GitTally refuses this file
|
||||
* below: "2.0" # GitTally decides how hard, see ConfigVersions.verdict
|
||||
* since: "0.9.16" # always hard: an older Werkator refuses this file
|
||||
* below: "2.0" # Werkator decides how hard, see ConfigVersions.verdict
|
||||
* ```
|
||||
*
|
||||
* There is deliberately no version of the file format itself (no `apiVersion`): no API is
|
||||
* involved — GitTally reads its own configuration — and only one configuration generation
|
||||
* involved — Werkator reads its own configuration — and only one configuration generation
|
||||
* is ever supported. The declared version exists to make an incompatibility nameable,
|
||||
* never to run two parsers.
|
||||
*/
|
||||
data class VersionRequirement(
|
||||
/** Oldest GitTally that understands this file; empty means the file does not say. */
|
||||
/** Oldest Werkator that understands this file; empty means the file does not say. */
|
||||
val since: String = "",
|
||||
/** First GitTally this file was not released for; empty means no ceiling. */
|
||||
/** First Werkator this file was not released for; empty means no ceiling. */
|
||||
val below: String = "",
|
||||
)
|
||||
|
||||
data class GitTallyMeta(
|
||||
data class WerkatorMeta(
|
||||
val version: VersionRequirement = VersionRequirement(),
|
||||
)
|
||||
|
||||
/** What a [VersionRequirement] means for the GitTally that reads the file. */
|
||||
/** What a [VersionRequirement] means for the Werkator that reads the file. */
|
||||
sealed interface VersionVerdict {
|
||||
/** The running version is covered by the declaration. */
|
||||
data object Compatible : VersionVerdict
|
||||
@@ -37,24 +37,24 @@ sealed interface VersionVerdict {
|
||||
val message: String,
|
||||
) : VersionVerdict
|
||||
|
||||
/** Not usable: the file predates a change that GitTally cannot bridge. */
|
||||
/** Not usable: the file predates a change that Werkator cannot bridge. */
|
||||
data class Incompatible(
|
||||
val message: String,
|
||||
) : VersionVerdict
|
||||
}
|
||||
|
||||
/** A configuration file this GitTally must not read; carries the file's name in its message. */
|
||||
/** A configuration file this Werkator must not read; carries the file's name in its message. */
|
||||
open class ConfigException(
|
||||
message: String,
|
||||
) : RuntimeException(message)
|
||||
|
||||
/** The file declares a GitTally that cannot read it, see [ConfigVersions]. */
|
||||
/** The file declares a Werkator that cannot read it, see [ConfigVersions]. */
|
||||
class ConfigVersionException(
|
||||
message: String,
|
||||
) : ConfigException(message)
|
||||
|
||||
/**
|
||||
* The file is written in a shape this GitTally no longer reads. Refusing it is the point:
|
||||
* The file is written in a shape this Werkator no longer reads. Refusing it is the point:
|
||||
* a key that moved and is silently ignored means a build that quietly stops happening.
|
||||
*/
|
||||
class ConfigFormatException(
|
||||
@@ -64,7 +64,7 @@ class ConfigFormatException(
|
||||
object ConfigVersions {
|
||||
/**
|
||||
* The version in which the configuration format last changed incompatibly — a file
|
||||
* written before it cannot be read by this GitTally. Empty while no such change has
|
||||
* written before it cannot be read by this werkator. Empty while no such change has
|
||||
* happened; set it to the release that introduces one, together with the migration
|
||||
* note the message points at.
|
||||
*/
|
||||
@@ -76,13 +76,13 @@ object ConfigVersions {
|
||||
/**
|
||||
* Decides what [requirement] means for [running].
|
||||
*
|
||||
* `since` is always hard — a file that needs a newer GitTally cannot be honored, and
|
||||
* `since` is always hard — a file that needs a newer Werkator cannot be honored, and
|
||||
* silently ignoring its unknown keys is exactly the failure mode this section exists
|
||||
* to prevent.
|
||||
*
|
||||
* `below` alone only warns: it is the team's release marker, and an unmaintained
|
||||
* marker must never stop a CI. Whether the running version really broke the file is
|
||||
* GitTally's own knowledge ([FORMAT_BROKE_IN]) — a file written before that change
|
||||
* Werkator's own knowledge ([FORMAT_BROKE_IN]) — a file written before that change
|
||||
* and read after it is incompatible regardless of what it declares as its ceiling.
|
||||
*/
|
||||
fun verdict(
|
||||
@@ -95,13 +95,13 @@ object ConfigVersions {
|
||||
val since = parse(requirement.since)
|
||||
if (since != null && version < since) {
|
||||
return VersionVerdict.Incompatible(
|
||||
"needs GitTally ${requirement.since} or newer (gitTally.version.since), this is $running",
|
||||
"needs Werkator ${requirement.since} or newer (werkator.version.since), this is $running",
|
||||
)
|
||||
}
|
||||
val broke = parse(brokeIn)
|
||||
if (since != null && broke != null && since < broke && version >= broke) {
|
||||
return VersionVerdict.Incompatible(
|
||||
"is written for GitTally ${requirement.since} (gitTally.version.since), " +
|
||||
"is written for Werkator ${requirement.since} (werkator.version.since), " +
|
||||
"but the configuration format changed incompatibly in $brokeIn" +
|
||||
brokeDescription.takeIf { it.isNotBlank() }?.let { ": $it" }.orEmpty(),
|
||||
)
|
||||
@@ -109,7 +109,7 @@ object ConfigVersions {
|
||||
val below = parse(requirement.below)
|
||||
if (below != null && version >= below) {
|
||||
return VersionVerdict.Warn(
|
||||
"was released for GitTally below ${requirement.below} (gitTally.version.below), this is $running",
|
||||
"was released for Werkator below ${requirement.below} (werkator.version.below), this is $running",
|
||||
)
|
||||
}
|
||||
return VersionVerdict.Compatible
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.config
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
import java.time.Duration
|
||||
|
||||
+10
-10
@@ -1,10 +1,10 @@
|
||||
package de.hoennig.gittally.config
|
||||
package de.hoennig.werkator.config
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
|
||||
data class GitTallyConfig(
|
||||
/** What this file declares about the GitTally that reads it; see [VersionRequirement]. */
|
||||
val gitTally: GitTallyMeta = GitTallyMeta(),
|
||||
data class WerkatorConfig(
|
||||
/** What this file declares about the Werkator that reads it; see [VersionRequirement]. */
|
||||
val werkator: WerkatorMeta = WerkatorMeta(),
|
||||
val server: ServerConfig = ServerConfig(),
|
||||
val git: GitConfig = GitConfig(),
|
||||
val gitea: GiteaConfig = GiteaConfig(),
|
||||
@@ -61,7 +61,7 @@ data class ServerConfig(
|
||||
)
|
||||
|
||||
/**
|
||||
* Opt-in managed nginx+certbot Docker container serving GitTally over HTTPS,
|
||||
* Opt-in managed nginx+certbot Docker container serving Werkator over HTTPS,
|
||||
* for hosts without a usable reverse proxy (ADR 0005). Off by default; the
|
||||
* reverse-proxy deployment from `docs/deployment.md` stays the recommended setup.
|
||||
*/
|
||||
@@ -76,11 +76,11 @@ data class NginxConfig(
|
||||
val httpsPort: Int = 8443,
|
||||
/** Host nginx proxies to; empty uses [serverName] (the container cannot reach `localhost`). */
|
||||
val upstreamHost: String = "",
|
||||
/** Name of the managed container; empty means `gittally-nginx-<repo-name>`. */
|
||||
/** Name of the managed container; empty means `werkator-nginx-<repo-name>`. */
|
||||
val containerName: String = "",
|
||||
/**
|
||||
* Directory for nginx config, certificates, and logs; empty means the platform
|
||||
* default `XDG_STATE_HOME` (or `~/.local/state`) + `/gittally/nginx/<repo-key>`.
|
||||
* default `XDG_STATE_HOME` (or `~/.local/state`) + `/werkator/nginx/<repo-key>`.
|
||||
*/
|
||||
val stateDir: String = "",
|
||||
/** E-mail for the Let's Encrypt account; empty registers without one. */
|
||||
@@ -96,7 +96,7 @@ data class GiteaConfig(
|
||||
val baseUrl: String = "",
|
||||
val owner: String = "",
|
||||
val repo: String = "",
|
||||
val statusContext: String = "GitTally",
|
||||
val statusContext: String = "werkator",
|
||||
)
|
||||
|
||||
data class ArtifactsConfig(
|
||||
@@ -117,7 +117,7 @@ data class ArtifactsConfig(
|
||||
val keepLatestGreen: Boolean = true,
|
||||
/**
|
||||
* Root directory for stored build artifacts; empty means the platform default
|
||||
* `XDG_STATE_HOME` (or `~/.local/state`) + `/gittally/artifacts/<repo-key>`.
|
||||
* `XDG_STATE_HOME` (or `~/.local/state`) + `/werkator/artifacts/<repo-key>`.
|
||||
*/
|
||||
val rootDir: String = "",
|
||||
)
|
||||
@@ -130,7 +130,7 @@ data class WatcherConfig(
|
||||
* Honor the `branches.<name>.requirePullRequest` gates. Set false for a plain git
|
||||
* origin without pull-request refs (no Gitea/GitHub) — gated branches then build
|
||||
* on new commits like any other branch. Typically overridden per machine in
|
||||
* `.git/gittally/.gittally.yml` when the committed config enables the gates.
|
||||
* `.git/werkator/.werkator.yml` when the committed config enables the gates.
|
||||
*/
|
||||
val pullRequestGate: Boolean = true,
|
||||
/**
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.git
|
||||
package de.hoennig.werkator.git
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.attribute.PosixFilePermissions
|
||||
@@ -15,10 +15,10 @@ object GitAskPass {
|
||||
#!/bin/sh
|
||||
case "${'$'}1" in
|
||||
*[Uu]sername*)
|
||||
printf '%s\n' "${'$'}GITTALLY_GIT_ACCOUNT"
|
||||
printf '%s\n' "${'$'}WERKATOR_GIT_ACCOUNT"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "${'$'}GITTALLY_GIT_TOKEN"
|
||||
printf '%s\n' "${'$'}WERKATOR_GIT_TOKEN"
|
||||
;;
|
||||
esac
|
||||
""".trimIndent() + "\n"
|
||||
@@ -30,7 +30,7 @@ object GitAskPass {
|
||||
): T {
|
||||
val script =
|
||||
Files.createTempFile(
|
||||
"gittally-askpass",
|
||||
"werkator-askpass",
|
||||
".sh",
|
||||
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")),
|
||||
)
|
||||
@@ -40,8 +40,8 @@ object GitAskPass {
|
||||
mapOf(
|
||||
"GIT_ASKPASS" to script.toAbsolutePath().toString(),
|
||||
"GIT_TERMINAL_PROMPT" to "0",
|
||||
"GITTALLY_GIT_ACCOUNT" to account,
|
||||
"GITTALLY_GIT_TOKEN" to token,
|
||||
"WERKATOR_GIT_ACCOUNT" to account,
|
||||
"WERKATOR_GIT_TOKEN" to token,
|
||||
),
|
||||
)
|
||||
} finally {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.git
|
||||
package de.hoennig.werkator.git
|
||||
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Path
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.git
|
||||
package de.hoennig.werkator.git
|
||||
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import java.nio.file.Path
|
||||
@@ -260,7 +260,7 @@ class GitService(
|
||||
|
||||
/**
|
||||
* The content of [path] as committed in [commit], or null when that commit has no
|
||||
* such file — used to read a branch's committed `.gittally.yml` without a worktree.
|
||||
* such file — used to read a branch's committed `.werkator.yml` without a worktree.
|
||||
*/
|
||||
fun showFileAtCommit(
|
||||
commit: String,
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
package de.hoennig.werkator.gitea
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.config.GitTallyConfig
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import de.hoennig.werkator.config.WerkatorConfig
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.client.JdkClientHttpRequestFactory
|
||||
@@ -170,7 +170,7 @@ class GiteaClient(
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEnabled(config: GitTallyConfig): Boolean =
|
||||
private fun isEnabled(config: WerkatorConfig): Boolean =
|
||||
config.gitea.baseUrl.isNotBlank() &&
|
||||
config.gitea.owner.isNotBlank() &&
|
||||
config.gitea.repo.isNotBlank() &&
|
||||
@@ -182,7 +182,7 @@ class GiteaClient(
|
||||
HttpClient.newBuilder().connectTimeout(REQUEST_TIMEOUT).build(),
|
||||
).apply { setReadTimeout(REQUEST_TIMEOUT) }
|
||||
|
||||
private fun restClient(config: GitTallyConfig): RestClient =
|
||||
private fun restClient(config: WerkatorConfig): RestClient =
|
||||
RestClient
|
||||
.builder()
|
||||
.requestFactory(requestFactory)
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.gitea
|
||||
package de.hoennig.werkator.gitea
|
||||
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
|
||||
/**
|
||||
* Gitea commit-status state published for this build status.
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.metrics
|
||||
package de.hoennig.werkator.metrics
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import java.time.Clock
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.metrics
|
||||
package de.hoennig.werkator.metrics
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.metrics
|
||||
package de.hoennig.werkator.metrics
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
@@ -30,7 +30,7 @@ data class PersistedMetricsState(
|
||||
* repository size every 60 seconds and keeps running min/max/avg per metric.
|
||||
* The aggregation state is persisted, so restarts continue the series.
|
||||
* Every source degrades gracefully: an unreadable source makes its metric null
|
||||
* (shown as `n/a`), never fails a sample. Like the [de.hoennig.gittally.watcher.Watcher],
|
||||
* (shown as `n/a`), never fails a sample. Like the [de.hoennig.werkator.watcher.Watcher],
|
||||
* nothing is scheduled until [start] is called (server mode only).
|
||||
*/
|
||||
class SystemMetricsCollector(
|
||||
@@ -83,7 +83,7 @@ class SystemMetricsCollector(
|
||||
scheduler =
|
||||
Executors
|
||||
.newSingleThreadScheduledExecutor { runnable ->
|
||||
Thread(runnable, "gittally-metrics").apply { isDaemon = true }
|
||||
Thread(runnable, "werkator-metrics").apply { isDaemon = true }
|
||||
}.also {
|
||||
it.scheduleWithFixedDelay(::sampleSafely, 0, SAMPLE_INTERVAL_SECONDS, TimeUnit.SECONDS)
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import java.time.Instant
|
||||
|
||||
/** JSON statuses are lowercase like the legacy TSV/HTML statuses. */
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.core.io.FileSystemResource
|
||||
import org.springframework.core.io.Resource
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactKeys
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.ArtifactKeys
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
+9
-9
@@ -1,12 +1,12 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.build.BuildExecutor
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.BuildExecutor
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
@@ -192,7 +192,7 @@ class BuildsApiController(
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TOKEN_HEADER = "X-GitTally-Token"
|
||||
const val TOKEN_HEADER = "X-werkator-Token"
|
||||
private const val MAX_LOG_CHUNK = 1024L * 1024L
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.SecretFiles
|
||||
import de.hoennig.werkator.SecretFiles
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.MessageDigest
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
/**
|
||||
* Generates the nginx configuration for the managed proxy container, ported from
|
||||
@@ -12,7 +12,7 @@ object NginxConfigFiles {
|
||||
* The `nginx.conf` content. Without [full] it is the init config for the
|
||||
* two-phase startup: HTTP only, serving the ACME webroot challenge and
|
||||
* redirecting everything else to HTTPS. With [full] an HTTPS server block
|
||||
* with the Let's Encrypt certificate and the proxy to GitTally is added.
|
||||
* with the Let's Encrypt certificate and the proxy to Werkator is added.
|
||||
*/
|
||||
fun nginxConf(
|
||||
serverName: String,
|
||||
+20
-20
@@ -1,9 +1,9 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactKeys
|
||||
import de.hoennig.gittally.build.DockerBuildRunner.Companion.GITTALLY_LABEL
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.git.GitCommandRunner
|
||||
import de.hoennig.werkator.build.ArtifactKeys
|
||||
import de.hoennig.werkator.build.DockerBuildRunner.Companion.WERKATOR_LABEL
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import de.hoennig.werkator.git.GitCommandRunner
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
import java.nio.file.Files
|
||||
@@ -11,10 +11,10 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
* Manages the opt-in nginx+certbot Docker container that serves GitTally over
|
||||
* Manages the opt-in nginx+certbot Docker container that serves Werkator over
|
||||
* HTTPS on hosts without a reverse proxy (ADR 0005), ported from the legacy
|
||||
* `start_artifact_nginx` subsystem. Shells out to the `docker` CLI via the
|
||||
* generic [GitCommandRunner] process wrapper, like [de.hoennig.gittally.build.DockerBuildRunner].
|
||||
* generic [GitCommandRunner] process wrapper, like [de.hoennig.werkator.build.DockerBuildRunner].
|
||||
*
|
||||
* Startup is two-phase: an HTTP-only init config serves the ACME webroot
|
||||
* challenge, the certificate is obtained via a certbot container, then nginx is
|
||||
@@ -197,7 +197,7 @@ class NginxProxyManager(
|
||||
/**
|
||||
* Legacy `cleanup_stale_artifact_nginx_containers`: remove the container by
|
||||
* name, all nginx-role containers of this repository by label, and any
|
||||
* GitTally container still occupying the configured ports.
|
||||
* Werkator container still occupying the configured ports.
|
||||
*/
|
||||
private fun cleanupStaleContainers(settings: NginxSettings) {
|
||||
commandRunner.run(listOf("docker", "rm", "-f", settings.containerName), workingDir)
|
||||
@@ -208,11 +208,11 @@ class NginxProxyManager(
|
||||
"ps",
|
||||
"-aq",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL=true",
|
||||
"label=$WERKATOR_LABEL=true",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL.repository=${settings.repoKey}",
|
||||
"label=$WERKATOR_LABEL.repository=${settings.repoKey}",
|
||||
"--filter",
|
||||
"label=$GITTALLY_LABEL.role=nginx",
|
||||
"label=$WERKATOR_LABEL.role=nginx",
|
||||
),
|
||||
workingDir,
|
||||
)
|
||||
@@ -220,11 +220,11 @@ class NginxProxyManager(
|
||||
commandRunner.run(listOf("docker", "rm", "-f") + labelled.lines(), workingDir)
|
||||
}
|
||||
for (container in listContainersUsingPorts(settings)) {
|
||||
if (container.labels.contains("$GITTALLY_LABEL=true") ||
|
||||
container.name.startsWith("gittally-") ||
|
||||
if (container.labels.contains("$WERKATOR_LABEL=true") ||
|
||||
container.name.startsWith("werkator-") ||
|
||||
container.name.startsWith("git-watch-origin-and-test-nginx-")
|
||||
) {
|
||||
log.info("removing stale GitTally container using an nginx port: {}", container.name)
|
||||
log.info("removing stale Werkator container using an nginx port: {}", container.name)
|
||||
commandRunner.run(listOf("docker", "rm", "-f", container.id), workingDir)
|
||||
}
|
||||
}
|
||||
@@ -303,11 +303,11 @@ class NginxProxyManager(
|
||||
"--volume",
|
||||
"${settings.nginxConf}:/etc/nginx/nginx.conf:ro",
|
||||
"--label",
|
||||
"$GITTALLY_LABEL=true",
|
||||
"$WERKATOR_LABEL=true",
|
||||
"--label",
|
||||
"$GITTALLY_LABEL.repository=${settings.repoKey}",
|
||||
"$WERKATOR_LABEL.repository=${settings.repoKey}",
|
||||
"--label",
|
||||
"$GITTALLY_LABEL.role=nginx",
|
||||
"$WERKATOR_LABEL.role=nginx",
|
||||
"nginx",
|
||||
)
|
||||
|
||||
@@ -405,16 +405,16 @@ class NginxProxyManager(
|
||||
System.getenv("XDG_STATE_HOME")?.takeIf { it.isNotBlank() }?.let { Paths.get(it) }
|
||||
?: Paths.get(System.getProperty("user.home"), ".local", "state")
|
||||
return stateHome
|
||||
.resolve("gittally")
|
||||
.resolve("werkator")
|
||||
.resolve("nginx")
|
||||
.resolve(ArtifactKeys.repoKey(repoDir))
|
||||
.toAbsolutePath()
|
||||
.normalize()
|
||||
}
|
||||
|
||||
/** Legacy default `gittally-nginx-<repo-name>` with unsafe characters replaced. */
|
||||
/** Legacy default `werkator-nginx-<repo-name>` with unsafe characters replaced. */
|
||||
fun defaultContainerName(repoDir: Path): String =
|
||||
"gittally-nginx-" + repoDir.fileName.toString().replace(Regex("[^A-Za-z0-9_.-]"), "-")
|
||||
"werkator-nginx-" + repoDir.fileName.toString().replace(Regex("[^A-Za-z0-9_.-]"), "-")
|
||||
|
||||
/**
|
||||
* Certbot's pinned DH parameters (RFC 7919 ffdhe2048), bundled as a resource:
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
@@ -12,5 +12,5 @@ class ServerConfiguration {
|
||||
* until the first guarded request, so the bean is safe outside a git repository.
|
||||
*/
|
||||
@Bean
|
||||
fun controlTokenService(): ControlTokenService = ControlTokenService(Paths.get(".git/gittally/control-token"))
|
||||
fun controlTokenService(): ControlTokenService = ControlTokenService(Paths.get(".git/werkator/control-token"))
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.metrics.SystemMetricsCollector
|
||||
import de.hoennig.werkator.metrics.SystemMetricsCollector
|
||||
import jakarta.annotation.PreDestroy
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent
|
||||
import org.springframework.context.annotation.Profile
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import jakarta.annotation.PreDestroy
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent
|
||||
@@ -26,7 +26,7 @@ class ServerNginxLifecycle(
|
||||
/** Replaceable for tests: the scheduler running startup and renewal checks. */
|
||||
internal var schedulerFactory: () -> ScheduledExecutorService = {
|
||||
Executors.newSingleThreadScheduledExecutor { runnable ->
|
||||
Thread(runnable, "gittally-nginx").apply { isDaemon = true }
|
||||
Thread(runnable, "werkator-nginx").apply { isDaemon = true }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.watcher.Watcher
|
||||
import de.hoennig.werkator.watcher.Watcher
|
||||
import jakarta.annotation.PreDestroy
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent
|
||||
import org.springframework.context.annotation.Profile
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.gitea.GiteaClient
|
||||
import de.hoennig.gittally.gitea.GiteaStatusResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.gitea.GiteaClient
|
||||
import de.hoennig.werkator.gitea.GiteaStatusResult
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.metrics.SystemMetrics
|
||||
import de.hoennig.gittally.metrics.SystemMetricsCollector
|
||||
import de.hoennig.werkator.metrics.SystemMetrics
|
||||
import de.hoennig.werkator.metrics.SystemMetricsCollector
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
+12
-12
@@ -1,14 +1,14 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.build.BuildExecutor
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.gittally.metrics.SystemMetricsCollector
|
||||
import de.hoennig.gittally.watcher.Watcher
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.BuildExecutor
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.config.ConfigFiles
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import de.hoennig.werkator.metrics.SystemMetricsCollector
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
import org.springframework.boot.info.BuildProperties
|
||||
@@ -28,7 +28,7 @@ import kotlin.streams.asSequence
|
||||
|
||||
/**
|
||||
* Server-rendered Thymeleaf views over the JSON API. The pages render the full
|
||||
* state server-side (usable without JavaScript); `gittally.js` then polls the
|
||||
* state server-side (usable without JavaScript); `werkator.js` then polls the
|
||||
* `/api/…` endpoints and re-renders the table bodies — pages are never re-fetched
|
||||
* and diffed like legacy, so the UI cannot get stuck on a loading animation.
|
||||
*/
|
||||
@@ -235,7 +235,7 @@ class UiController(
|
||||
try {
|
||||
configLoader.loadWithBranchLayer(
|
||||
workingDir,
|
||||
gitService.showFileAtCommit(result.commit, Watcher.CONFIG_FILE, workingDir),
|
||||
ConfigFiles.readCommitted { gitService.showFileAtCommit(result.commit, it, workingDir) },
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
configLoader.load(workingDir)
|
||||
+9
-9
@@ -1,9 +1,9 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.build.BuildResult
|
||||
import de.hoennig.gittally.config.GiteaConfig
|
||||
import de.hoennig.gittally.metrics.MetricAggregate
|
||||
import de.hoennig.gittally.metrics.SystemMetrics
|
||||
import de.hoennig.werkator.build.BuildResult
|
||||
import de.hoennig.werkator.config.GiteaConfig
|
||||
import de.hoennig.werkator.metrics.MetricAggregate
|
||||
import de.hoennig.werkator.metrics.SystemMetrics
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Duration
|
||||
@@ -32,7 +32,7 @@ class GiteaWebLinks(
|
||||
.joinToString("/") { URLEncoder.encode(it, StandardCharsets.UTF_8).replace("+", "%20") }
|
||||
}
|
||||
|
||||
/** Display formatting shared by the server-rendered views; `gittally.js` renders the same formats. */
|
||||
/** Display formatting shared by the server-rendered views; `werkator.js` renders the same formats. */
|
||||
object UiFormats {
|
||||
private val timestampFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault())
|
||||
|
||||
@@ -69,7 +69,7 @@ object UiFormats {
|
||||
/**
|
||||
* CSS class highlighting a critical utilization: `metric-warn` from 80% of [total],
|
||||
* `metric-crit` from 90%, empty below or when either value is unavailable.
|
||||
* `gittally.js` (`utilizationClass`) must apply the same thresholds.
|
||||
* `werkator.js` (`utilizationClass`) must apply the same thresholds.
|
||||
*/
|
||||
fun utilizationClass(
|
||||
used: Double?,
|
||||
@@ -176,7 +176,7 @@ data class LogFileView(
|
||||
val failed: Boolean,
|
||||
)
|
||||
|
||||
/** One card of the current-builds view; the live log is fetched by `gittally.js`. */
|
||||
/** One card of the current-builds view; the live log is fetched by `werkator.js`. */
|
||||
data class CurrentBuildView(
|
||||
val branch: String,
|
||||
/** The displayed build name; = [branch] unless a named auto-build slot triggered the build. */
|
||||
@@ -194,7 +194,7 @@ data class CurrentBuildView(
|
||||
|
||||
/**
|
||||
* One row of the system-metrics table. The [key] matches the JSON field of
|
||||
* `GET /api/system`, so `gittally.js` can update the cells in place.
|
||||
* `GET /api/system`, so `werkator.js` can update the cells in place.
|
||||
*/
|
||||
data class MetricRowView(
|
||||
val key: String,
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package de.hoennig.gittally.server
|
||||
package de.hoennig.werkator.server
|
||||
|
||||
import de.hoennig.gittally.watcher.Watcher
|
||||
import de.hoennig.gittally.watcher.WatcherState
|
||||
import de.hoennig.werkator.watcher.Watcher
|
||||
import de.hoennig.werkator.watcher.WatcherState
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.watcher
|
||||
package de.hoennig.werkator.watcher
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
@@ -74,7 +74,7 @@ object AutoBuildSlots {
|
||||
|
||||
/**
|
||||
* Persists which auto-build slots already triggered as a JSON file,
|
||||
* e.g. `.git/gittally/auto-builds.json` (replaces the legacy `auto-builds.tsv`).
|
||||
* e.g. `.git/werkator/auto-builds.json` (replaces the legacy `auto-builds.tsv`).
|
||||
* Entries of past days are dropped on write, so the file never grows unbounded.
|
||||
*/
|
||||
class FileAutoBuildState(
|
||||
+28
-25
@@ -1,16 +1,17 @@
|
||||
package de.hoennig.gittally.watcher
|
||||
package de.hoennig.werkator.watcher
|
||||
|
||||
import de.hoennig.gittally.build.ArtifactKeys
|
||||
import de.hoennig.gittally.build.ArtifactStore
|
||||
import de.hoennig.gittally.build.BuildExecutor
|
||||
import de.hoennig.gittally.build.BuildResultRepository
|
||||
import de.hoennig.gittally.build.BuildStatus
|
||||
import de.hoennig.gittally.build.GitWorktreeWorkspaces
|
||||
import de.hoennig.gittally.config.BuildDefinition
|
||||
import de.hoennig.gittally.config.ConfigLoader
|
||||
import de.hoennig.gittally.config.DurationParser
|
||||
import de.hoennig.gittally.config.GitTallyConfig
|
||||
import de.hoennig.gittally.git.GitService
|
||||
import de.hoennig.werkator.build.ArtifactKeys
|
||||
import de.hoennig.werkator.build.ArtifactStore
|
||||
import de.hoennig.werkator.build.BuildExecutor
|
||||
import de.hoennig.werkator.build.BuildResultRepository
|
||||
import de.hoennig.werkator.build.BuildStatus
|
||||
import de.hoennig.werkator.build.GitWorktreeWorkspaces
|
||||
import de.hoennig.werkator.config.BuildDefinition
|
||||
import de.hoennig.werkator.config.ConfigFiles
|
||||
import de.hoennig.werkator.config.ConfigLoader
|
||||
import de.hoennig.werkator.config.DurationParser
|
||||
import de.hoennig.werkator.config.WerkatorConfig
|
||||
import de.hoennig.werkator.git.GitService
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import java.nio.file.Files
|
||||
@@ -80,7 +81,7 @@ class Watcher(
|
||||
scheduler =
|
||||
Executors
|
||||
.newSingleThreadScheduledExecutor { runnable ->
|
||||
Thread(runnable, "gittally-watcher").apply { isDaemon = true }
|
||||
Thread(runnable, "werkator-watcher").apply { isDaemon = true }
|
||||
}.also {
|
||||
it.scheduleWithFixedDelay({ pollSafely(workingDir) }, 0, interval.toMillis(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
@@ -207,7 +208,7 @@ class Watcher(
|
||||
}
|
||||
|
||||
private fun enqueueDueBranches(
|
||||
config: GitTallyConfig,
|
||||
config: WerkatorConfig,
|
||||
originBranches: Set<String>,
|
||||
workingDir: Path,
|
||||
) {
|
||||
@@ -238,7 +239,7 @@ class Watcher(
|
||||
|
||||
/**
|
||||
* The build definitions that apply to [branch]: the primary configuration with the
|
||||
* branch's own committed `.gittally.yml` merged on top (the pinned keys stripped),
|
||||
* branch's own committed `.werkator.yml` merged on top (the pinned keys stripped),
|
||||
* so a new `builds` configuration can be tried out on a branch without touching any
|
||||
* other branch's builds. A branch's definitions only ever apply to that branch —
|
||||
* their selectors are evaluated for it alone, so a definition committed on one branch
|
||||
@@ -254,15 +255,17 @@ class Watcher(
|
||||
branch: String,
|
||||
headCommit: String?,
|
||||
workingDir: Path,
|
||||
primary: GitTallyConfig,
|
||||
primary: WerkatorConfig,
|
||||
): Map<String, BuildDefinition> {
|
||||
val commit = headCommit ?: return primary.effectiveBuildDefinitions()
|
||||
branchDefinitions[branch]?.takeIf { it.commit == commit && it.primary == primary }?.let { return it.definitions }
|
||||
val definitions =
|
||||
try {
|
||||
configLoader
|
||||
.loadWithBranchLayer(workingDir, gitService.showFileAtCommit(commit, CONFIG_FILE, workingDir))
|
||||
.effectiveBuildDefinitions()
|
||||
.loadWithBranchLayer(
|
||||
workingDir,
|
||||
ConfigFiles.readCommitted { gitService.showFileAtCommit(commit, it, workingDir) },
|
||||
).effectiveBuildDefinitions()
|
||||
} catch (e: Exception) {
|
||||
log.warn(
|
||||
"ignoring the committed {} of branch {} at {}: {}",
|
||||
@@ -279,7 +282,7 @@ class Watcher(
|
||||
|
||||
private class CachedDefinitions(
|
||||
val commit: String,
|
||||
val primary: GitTallyConfig,
|
||||
val primary: WerkatorConfig,
|
||||
val definitions: Map<String, BuildDefinition>,
|
||||
)
|
||||
|
||||
@@ -304,7 +307,7 @@ class Watcher(
|
||||
private fun startBuildIfDue(
|
||||
branch: String,
|
||||
allowSameCommit: Boolean,
|
||||
config: GitTallyConfig,
|
||||
config: WerkatorConfig,
|
||||
pullRequestHeads: Lazy<Set<String>>,
|
||||
workingDir: Path,
|
||||
build: String = BuildDefinition.DEFAULT,
|
||||
@@ -335,7 +338,7 @@ class Watcher(
|
||||
* the point of a scheduled build.
|
||||
*/
|
||||
private fun enqueueScheduledBuilds(
|
||||
config: GitTallyConfig,
|
||||
config: WerkatorConfig,
|
||||
originBranches: Set<String>,
|
||||
heads: Map<String, String>,
|
||||
pullRequestHeads: Lazy<Set<String>>,
|
||||
@@ -373,7 +376,7 @@ class Watcher(
|
||||
* `builds` entry with `atTimes` and a single-branch selector would do.
|
||||
*/
|
||||
private fun enqueueDeprecatedAutoBuilds(
|
||||
config: GitTallyConfig,
|
||||
config: WerkatorConfig,
|
||||
originBranches: Set<String>,
|
||||
pullRequestHeads: Lazy<Set<String>>,
|
||||
workingDir: Path,
|
||||
@@ -413,7 +416,7 @@ class Watcher(
|
||||
|
||||
/** Results first, then artifacts of dropped results, then worktrees of branches gone from origin. */
|
||||
private fun prune(
|
||||
config: GitTallyConfig,
|
||||
config: WerkatorConfig,
|
||||
originBranches: List<String>,
|
||||
workingDir: Path,
|
||||
) {
|
||||
@@ -463,9 +466,9 @@ class Watcher(
|
||||
|
||||
companion object {
|
||||
/** Auto-build trigger state next to the build results (replaces legacy `auto-builds.tsv`). */
|
||||
const val AUTO_BUILDS_FILE = ".git/gittally/auto-builds.json"
|
||||
const val AUTO_BUILDS_FILE = ".git/werkator/auto-builds.json"
|
||||
|
||||
/** The committed config read per branch for its build definitions. */
|
||||
const val CONFIG_FILE = ".gittally.yml"
|
||||
const val CONFIG_FILE = ConfigFiles.COMMITTED
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.watcher
|
||||
package de.hoennig.werkator.watcher
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.hoennig.gittally.watcher
|
||||
package de.hoennig.werkator.watcher
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
Reference in New Issue
Block a user