jj is a command line Jenkins job runner. It requires a lightweight
configuration.
The jenkins-cli utility (docs) does much of what jj does, but it isn’t easy
to create configurations that are both secure and easy shortcuts. jj strives
to be a better mechanism in which to kick off and watch jobs as well.
A short list of ways in which jj differs from jenkins-cli:
- Authentication can be made secure (via
passor environment variables). - Servers are easy to reference to reduce invocation complexity at the command line.
- Jobs themselves are also easy to reference to reduce command line invocation complexity.
- Jobs can be watched (coming soon!) - new builds on a job are automatically
displayed in a persistent run. A sort of
tail -ffor a job. - Child jobs can similarly be configured to watch (coming soon!).
jjcan be used for similar job invocation / watching on non-Jenkins systems (Concourse, GitHub Actions, etc). Also coming soon!
The utility structure is one of:
jj <job name> [-s/--server <server-name>] [-v...]
Omitting [--server] will require jj to find the defaultServer in
~/.config/jj/config.toml. The value of defaultServer must correspond to a
TOML section, which represents the server to be contacted.
Much like ssh, use -v to increase verbosity in the logging, with subsequent
v’s increasing the verbosity further (such as -vv and -vvv). Beware that
at TRACE the API token is logged!
default_server = "foo" [foo] host_url = "http://jenkins.foo:8080" username = "jeeves" token_eval = "printf '%s' 'totally secure token'" [secure] host_url = "https://jenkins.foo" username = "jeeves" token_eval = "pass jenkins-foo-api-token"
Coming soon!
When running without a configuration file, the notion of a default_server
isn’t as strong.
Simply use --param foo=bar or -P foo=bar (add more --params / -P uses
for additional parameters) to provide parameters to the build. Otherwise its
defaults will be assumed for all missing parameters.
Coming soon!
jj can print log messages using -v and adding more v’s will increase
verbosity further like it does for ssh. So for the second level of verbosity,
use -vv, the third -vvv, and so on.
Beware that at -vvvv, jj will print your API token! This is considered
helpful in the even that your token_eval is having trouble and you need to see
the output of it.
All logging goes to stderr so you can be verbose and continue do any
processing of the job output (going to stdout) without any changes.
The configuration file is to optimize common, manual usage of the program. The configuration file should not be required to perform execution, so long as all of the arguments necessary are provided.
Documentation comes first. A feature is not done (and arguably not started) until there is documentation of both its intent and usage.
Unit tests inevitably require mocks, which are lies. In all practicality we must eventually mock something, but the internals of the application are not necessary to mock. Our tests all share the entry point of the program itself, and do not exercise some tiny portion of the program with the hopes that the mocks replicate the correct behavior.
If functionality cannot be covered with a test, then the functionality is doomed to regress from future changes. All functionality must have 100% test coverage.
We will attempt to make contributions welcome, even if they do not follow the above principles. That being said, expect adjustments or requests to the contributions to bring it into alignment with the above principles.
This repository follows the same convention as Go’s cmd/ pattern: the
library crate is the product and the binary crate is the frontend. The
library owns all domain logic — external service communication, business
rules, data types, and error types — including any I/O that is intrinsic to
the domain (HTTP calls, database access, file reads, and so on). The binary’s
sole responsibilities are parsing arguments, reading configuration files,
initialising logging, writing to stdout/stderr, and returning exit codes.
The formal name for this is the library-first design, related to Hexagonal Architecture’s notion of ports and adapters. That pattern would go further and abstract all I/O behind traits, allowing transports to be swapped out. We deliberately stop short of that: the external interactions this library performs are the domain, not infrastructure to be abstracted away. There is nothing meaningful to inject.
The practical test: if another team wanted to embed this functionality in their own tool, they should be able to import the library crate directly without touching the binary crate.