Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The plugins

Some files mean more than their text: a CI job is its includes and its extends chain merged, a dependency’s version may be set in a parent three files away. A plugin reads one such kind of file and answers for it through Otōto’s own tools, outline and read, so that what comes back is the thing as its tool would evaluate it, each line with the file and line it came from.

Nine come with Otōto. The installer puts them in ~/.config/ototo/plugins; each has its own version and is published on its own, between Otōto’s releases.

PluginReadsAsk it
gitlab-ciGitLab CI pipelinesread .gitlab-ci.yml#deploy:prod
composedocker compose filesread compose.yaml#web
mavenMaven POMsread pom.xml#spring-core
spring-configSpring’s application* and bootstrap* filesread application.yml#spring.datasource.url
terraformTerraform and OpenTofu modulesread main.tf#aws_db_instance.main@prod
kubeKustomize overlays and Helm valuesread overlays/prod/kustomization.yaml#Deployment/api
codeownersCODEOWNERSread .github/CODEOWNERS#src/api/server.rs
archiveJARs, WARs, wheels and other ZIPs; compiled Java classesread lib/core.jar!org.demo.Shelf
gitlabGitLab itself: merge requests and pipelinesthe forge tool

ototo.dev/plugins has the same list as the download channel has it today, with each one’s version.

Managing them

ototo plugins

lists the ones installed: on or off, version, who signed each, the files it reads, and why one does not load.

CommandDoes
ototo plugins available [tag]what the channel offers (java, infrastructure, gitlab…), and how each stands to the one here
ototo plugins add <plugin>installs one from the channel
ototo plugins updatebrings the installed ones up to the channel’s; never back a version, and a plugin of your own is left alone
ototo plugins remove <plugin>deletes one
ototo plugins help <plugin>which of this repository’s files it reads, and its own notes
ototo plugins grant <plugin>lets a forge plugin reach the hosts it declares (revoke takes that back)

Nothing is trusted for having been downloaded. The channel’s index must be signed by the release key built into Otōto; a plugin’s file must have the checksum the index gives; and the plugin must then carry its own signature, by a key in your ~/.config/ototo/allowed_signers. The channel is asked only when you run one of these commands.

How it works says what a plugin can and cannot reach; Writing a plugin builds one from an empty directory.

gitlab-ci

Pipelines outlined a line per job and template, with stages, include, variables and default. read .gitlab-ci.yml#deploy:prod (or read deploy:prod from anywhere, #deploy:prod.script for one key) returns the job as GitLab runs it: local includes followed, the extends chain deep-merged, default and global variables inherited unless inherit says not, anchors and !reference expanded, and every key marked with the file, lines and job or template it came from. Project, remote, template and component includes are named, not followed.

compose

Compose files and their overlays outlined a line per service. read compose.yaml#web returns the service as docker compose runs it: includes, the file and its override (or an overlay’s base under it) merged as compose merges them, extends resolved, anchors expanded, and ${VAR} filled in from the project’s .env, with each line’s source. Variables come from .env only: a plugin sees no environment.

maven

POMs outlined as coordinates, parent, modules, properties, dependency management, dependencies, build plugins and profiles. read pom.xml#spring-core returns the dependency with its effective version and scope as Maven resolves them: parents found by relativePath or by coordinates, properties merged and ${…} filled in, a version not declared taken from dependency management in Maven’s order, imported BOMs included. A parent or BOM outside the repository is named, and no version is made up. Also #effective, #properties, #parent, #modules.

With dependency_caches = true in Otōto’s settings, a parent or a BOM that is not in the repository is read where Maven keeps what it has downloaded, ~/.m2/repository, and the dependency’s JAR is named when it is there. From version 0.2 of the plugin, and an Otōto later than 2026.19.

spring-config

application* and bootstrap* .properties, .yml and .yaml, read with the files Spring loads beside them. read application.yml#spring.datasource.url gives the value with no profile active and for each profile that changes it, each with its file and line and what it overrides; #@prod gives every property as that profile sees it. Placeholders are filled in; a key no file sets shows as coming from the environment, never guessed.

terraform

Modules read with their variables’ values as Terraform gives them: the default, the tfvars files in its order, then an environment’s (@prod). read main.tf#aws_db_instance.main@prod gives the block as written, each expression that uses a variable or a local marked with what it works out to, and where every value came from. read var.region gives a variable’s declaration, value, every file that sets it and every use. No function is called, sensitive variables are never shown, and state, workspaces and remote modules are out of sight.

kube

Kustomize and Helm, from the repository’s files alone: no kustomize, kubectl or helm runs. read overlays/prod/kustomization.yaml#Deployment/api builds the resource in kustomize’s order (resources, components, generators, patches, namespace, prefixes, labels, replicas, images), each line with the file that set it and what changed it. read values.yaml#image.tag gives a chart’s value across its values files and the template lines that use it. Remote bases and generator plugins are named, not applied; templates are not rendered.

codeowners

Any CODEOWNERS, outlined as its rules. read .github/CODEOWNERS#src/api/server.rs answers who owns that path: the owners, the rule that decides with its line, and the earlier rules it overrides; a GitLab file with sections is answered section by section. #@backend lists the lines that name an owner. It says whether it read the patterns as GitHub or as GitLab does, and when that cannot be told and the two differ.

archive

Inside a JAR, WAR, wheel or any other ZIP without unpacking it, and a compiled Java class as its declarations, with no javap: outline lib/core.jar lists it, read lib/core.jar!org.demo.Shelf gives a class with its generics, parameter names and constants, and !META-INF/MANIFEST.MF any other entry; a.war!WEB-INF/lib/b.jar!… reaches inside. It is lent only the bytes of the files it opens, and writes and runs nothing. With dependency_caches = true, it reads a dependency’s JAR where the build tool keeps it.

gitlab

A forge plugin: it answers the forge tool’s questions about the repository on gitlab.com, or a GitLab of your own added in its settings. The checked-out branch’s open merge request and latest pipeline, a change by number, a pipeline’s jobs by stage, a failed job’s log cut to what failed. It reaches nothing until you grant it:

ototo plugins grant gitlab

It may only GET, over https, from the hosts it declares, and it never sees a token: Otōto adds the one you set for that host. Without a token it sees what is public; job logs need one. ototo plugins help gitlab has its settings.