Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 176 additions & 12 deletions docs/_ecosystem_8x/devkit/jig.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,35 @@ $ tree demo
demo
├── CHANGELOG.md
├── data
   └── common.yaml
└── common.yaml
├── examples
├── files
├── Gemfile
├── hiera.yaml
├── jig.toml
├── manifests
   └── init.pp
└── init.pp
├── metadata.json
├── Rakefile
├── README.md
├── spec
│   ├── classes
│   │   └── init_spec.rb
│   ├── default_facts.yml
│   └── spec_helper.rb
│ ├── acceptance
│ │ └── init_spec.rb
│ ├── classes
│ │ └── init_spec.rb
│ ├── default_facts.yml
│ ├── spec_helper.rb
│ └── spec_helper_acceptance.rb
├── tasks
└── templates

9 directories, 11 files
9 directories, 14 files
```

Two of those files are written by Jig itself rather than from a template: `metadata.json`, and `jig.toml`, which holds the [per-module settings](#per-module-configuration-jigtoml) described below.
The generated `metadata.json` declares its runtime requirement as `openvox` (`>= 7.0.0 < 9.0.0`), which the Vox Pupuli test tooling understands alongside the older `puppet` name.
It also ships dotfiles that `tree` hides: `.editorconfig`, `.gitignore`, `.overcommit.yml`, `.rubocop.yml`, and a `.devcontainer/` for editors that support it.

### Adding content to a module

Jig knows how to add other content to your module.
Expand Down Expand Up @@ -140,8 +148,53 @@ author = "John Doe"
license = "Apache-2.0"
forge_token = "your-forge-token"
template_dir = "~/.config/jig/templates"

# Trust unknown ssh host keys when fetching remote templates (for CI).
ssh_accept_new = false

# Run the Ruby-backed commands in a container instead of on the host.
[runner]
type = "local" # "local" (default) or "voxbox"
engine = "docker" # "docker" (default) or "podman"
image = "ghcr.io/voxpupuli/voxbox:latest"
```

Every field can also be set with a `JIG_`-prefixed environment variable (`JIG_FORGE_USERNAME`, `JIG_TEMPLATE_DIR`, `JIG_RUNNER_TYPE`, and so on), which takes precedence over the file.
Pass `--config` to point at a different file.

This file holds settings that belong to you: credentials, interview defaults, where your templates live.
Settings that belong to a module live in `jig.toml` instead.

### Per-module configuration (`jig.toml`)

`jig new module` writes a `jig.toml` next to `metadata.json`, and you commit it with the module so everyone working on it shares the same settings.
All three sections are optional; a missing section means Jig's defaults.

```toml
# Template repository the module was scaffolded from; later jig commands
# in this module default to it.
[template]
url = "ssh://git@my.git.server/jig_templates.git"
ref = "main"
commit = "<commit the templates were fetched at>"

# Files `jig renew` may re-render and overwrite. Empty by default so
# nothing is overwritten accidentally.
[renew]
paths = []

# Which files go into the module package built by `jig build`.
[build]
action = "deny"
exceptions = []
```

`[template]` and `[renew]` are covered in the next two sections; `[build]` is covered on the [publishing page](publishing.html#what-gets-packaged).

Trust settings such as `ssh_accept_new` are deliberately never read from `jig.toml`.
A cloned repository must not be able to change security behavior for the people who clone it.
{: .tip }

## Maintaining your own content templates

Jig embeds templates for all the kinds of content that it knows how to scaffold.
Expand All @@ -153,18 +206,129 @@ jig templates dump ~/.config/jig/templates

Any template found in your directory takes precedence over the embedded default, and any template you don't override falls back to the embedded version, so you only need to include the files you want to change.

To tell Jig where your templates live, use either of the following:
Two rules govern how a template becomes a file in the module:

* A file ending in `.tmpl` is rendered with Go's [text/template](https://pkg.go.dev/text/template) and written with the suffix stripped, so `README.md.tmpl` becomes `README.md`.
Every other file is copied byte-for-byte.
This is what lets a GitHub Actions workflow, which uses `{% raw %}{{ ... }}{% endraw %}` for its own purposes, sit in a template tree unescaped: leave the `.tmpl` suffix off and it's copied as-is.
* The `module/` directory of the template tree mirrors the generated module exactly.
There's no mapping in Jig's source, so you can add files Jig knows nothing about.
Drop `module/.github/workflows/ci.yml` into your template directory and every module you scaffold gets it.

* the `--template-dir` (`-t`) flag on `jig new`, which takes precedence, or
`metadata.json` and `jig.toml` are the exceptions: Jig always generates those itself and ignores (with a warning) any copy in a template tree.

To tell Jig where your templates live, use any of the following, in order of precedence:

* the `--template-dir` (`-t`) flag on `jig new`,
* the `JIG_TEMPLATE_DIR` environment variable, or
* the `template_dir` key in your Jig config file.

-----
When something doesn't render the way you expect, `jig templates resolve` shows exactly where a template name is being loaded from and every path Jig checked on the way:

```console
$ jig templates resolve class/class.pp
no external template directory configured; using embedded templates only
looking for templates/class/class.pp.tmpl (embedded) ... found
resolved class/class.pp to embedded template templates/class/class.pp.tmpl (rendered with text/template)
```

### Sharing templates from a git repository

A directory on disk works for one person.
For a team, point Jig at a git repository instead and everyone scaffolds from the same source without keeping a checkout at the same local path:

```console
jig new module --template-url 'ssh://git@my.git.server/jig_templates.git' --template-ref main mymodule
```

Jig makes a shallow clone into a temporary directory, uses it exactly like a `--template-dir`, and deletes the clone afterwards.
The repository layout is the same as a dumped template directory.
It then records the URL, ref, and commit in the module's `jig.toml` under `[template]`, so later `jig new class` and similar commands inside that module use the same templates with no flags at all.
An explicit `--template-dir` or `--template-url` overrides the recorded values.

ssh URLs authenticate through your running ssh-agent; https URLs are anonymous only.
On first contact with an unknown host Jig shows the key fingerprint and asks, like OpenSSH does.
In CI, pass `--ssh-accept-new` (or set `ssh_accept_new = true` in your config, or export `JIG_SSH_ACCEPT_NEW=true`) to accept unknown hosts automatically.
A host key that has _changed_ always fails with no override; if a server legitimately rotated its key, run `ssh-keygen -R <host>` and try again.

## Refreshing a module from its templates

When you change a template, `jig renew` re-renders the affected files in an existing module and overwrites them, so a change to (say) your standard `Gemfile` can be rolled out across many modules without hand-editing each one.

It only touches files matching the `[renew]` allowlist in the module's `jig.toml`, and that list is empty by default, so nothing can be overwritten until the module opts in:

```toml
[renew]
paths = [".github/**", "Gemfile", "Rakefile", "spec/spec_helper.rb"]
```

Patterns are gitignore-style globs relative to the module root.
Allowlisted files whose rendered content differs are overwritten, files that already match are left alone, and allowlisted files the module doesn't have yet are created.
`metadata.json` and `jig.toml` are never renewed.

Run it with `--dry-run` first to see a diff of every file that would change without writing anything:

```console
jig renew --dry-run
jig renew
```

The template source resolves the same way as for `jig new`: flags first, then `[template]` in `jig.toml` (re-fetching the latest commit of the recorded ref), then `template_dir` from your config.
After a successful renew from a remote repository, Jig rewrites `jig.toml` with the commit it fetched, so hand-written comments in that file don't survive.

If you maintain many modules with [ModuleSync](modulesync.html), note the difference: `jig renew` is Jig's own template-driven refresh of one module, while `jig msync update` runs ModuleSync.
Pick one mechanism per file; having both manage the same `Gemfile` will end in a tug of war.
{: .tip }

## Running the Ruby-backed commands

Three commands wrap the `bundle exec` tooling described elsewhere in this guide, so you can use one CLI for everything:

| Command | Runs |
|---------|------|
| `jig validate` | `rake validate`, `rake lint`, and `rake rubocop`, each as its own invocation, stopping at the first failure. `-s`, `-l`, and `-r` select a subset. |
| `jig test unit` | `rake spec`, or `rake parallel_spec` with `--parallel`. |
| `jig msync` | `msync` with whatever arguments you pass, for example `jig msync update`. |

Arguments after `--` are passed through verbatim to the underlying command.
Because these shell out to Bundler, they need the module's gems installed (`bundle install`) just as the direct commands do.

If you'd rather not maintain a Ruby toolchain on the host, set `type = "voxbox"` in the `[runner]` section of your config file (or export `JIG_RUNNER_TYPE=voxbox`).
Jig then runs the same three commands inside the [VoxBox](voxbox.html) container, mounting the module at `/repo`, so the only host dependency is Docker or Podman.
This is the most practical route on Windows, where a system-wide Bundler install is awkward.

## Migrate an existing module to Jig

Jig offers the possibility to make any PDK based module compatible with the OpenVox DevKit.
`jig convert` brings an existing module onto the toolchain that Jig's other commands expect.
Run it from the module's root directory and it overwrites `Gemfile`, `Rakefile`, and `spec/spec_helper.rb` with the same templates `jig new module` uses, creating `spec/` if needed:

```console
$ jig convert
convert successful: Gemfile, Rakefile, spec/spec_helper.rb
```

It works on PDK-generated and hand-maintained modules alike, including modules old enough to predate `metadata.json`.
Before it touches the three files above, it looks at the module's `metadata.json`:

* **Missing.** Jig creates it, and writes a `jig.toml` as well if the module doesn't have one.
If a Puppet 3-era `Modulefile` is present, its `name`, `version`, `author`, `license`, `summary`, `source`, `project_page`, and `dependency` lines pre-fill the new file, and the `Modulefile` is left in place with a warning that you can delete it.
Otherwise Jig runs the same interview as `jig new module`, taking the module name from the directory name (`puppet-nftables` gives `nftables`).
Pass `--skip-interview` (`-i`) together with the `-u`, `-a`, `-l`, `-s`, and `-S` flags to answer non-interactively; a flag wins over the `Modulefile`, which wins over the defaults in your config file.
* **Present but not valid JSON.** Jig prints the parse error and stops without changing anything.
* **Present but incomplete.** Jig fills in a default `version` (`0.1.0`) and empty `dependencies`, `requirements`, `operatingsystem_support`, and `tags` lists where they're missing, warns about anything else that fails validation (a missing `author` or `source`, say), and never overwrites a value that's already there.
Keys Jig doesn't know about, such as a PDK-era `pdk-version`, are kept, and an `operatingsystem_support` written as a bare list of OS names (the pre-2014 format) is rewritten in the current object form.
Running it a second time changes nothing.
* **Present and valid.** Left alone.

After writing the three files, Jig removes a `Gemfile.lock` if one is present, because it locks the dependency set of the `Gemfile` that was just replaced and would make the next `bundle install` fail.
Other PDK-era files (`.sync.yml`, `.pdkignore`, `.rubocop.yml`, `.puppet-lint.rc`, `.fixtures.yml`, `.vscode/`) are left in place, since they might hold customizations you want to keep, but Jig warns about each one it finds; nothing in Jig or VoxBox reads them.

Add `--dry-run` to see what would be created, overwritten, or removed without writing anything.
The `metadata.json` handling and the lock file cleanup need Jig 2.4.0 or later; earlier releases stop with `metadata.json not found` on a module that lacks the file.

Unlike `jig renew`, it always uses Jig's embedded templates, ignoring `--template-dir` and the module's `jig.toml`, and it needs no allowlist.

Please follow the instructions mentioned in the [migration page](migrating.html)
If you're coming from the PDK, the [migration page](migrating.html) maps each `pdk` command to its Jig or `bundle exec` equivalent.

## Alternative scaffolding solutions

Expand Down
3 changes: 2 additions & 1 deletion docs/_ecosystem_8x/devkit/linting.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ manifests/init.pp:10:manifest_whitespace_double_newline_end_of_file:ERROR:there
Try running the `lint_fix` task instead and you'll see most or all of the offenses marked as `FIXED` instead of `ERROR` or `WARNING` indicating that it has fixed the source files for you.
Some offenses cannot be fixed automatically and you may have to update them yourself.

If you use Jig, `jig validate` runs the `validate` and `lint` tasks together (equivalent to `bundle exec rake validate lint`).
If you use Jig, `jig validate` runs the `validate`, `lint`, and `rubocop` tasks in turn (equivalent to `bundle exec rake validate lint rubocop`), stopping at the first failure.
Pass `-sl` to run only the syntax and lint checks and skip Rubocop.
It doesn't run `lint_fix`, so run the `lint_fix` task directly when you want automatic fixes.

### Configuring the Puppet linter
Expand Down
26 changes: 19 additions & 7 deletions docs/_ecosystem_8x/devkit/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ When migrating away from the PDK, the biggest change you'll notice that instead
Most are shipped as gems that you'll add to a module's `Gemfile`.
This means that you'll maintain your own Ruby and Bundler installs, but most other tooling will be accessed via `bundle exec` commands in individual module repositories.

Jig 1.5.0 and later includes a `jig convert` command that migrates a PDK-based module for you.
It rewrites the module's `Gemfile`, `Rakefile`, and `spec/spec_helper.rb` to OpenVox- and VoxBox-compatible versions.
The `jig convert` command migrates a PDK-based module for you.
Run it from the module's root directory and it rewrites the module's `Gemfile`, `Rakefile`, and `spec/spec_helper.rb` to OpenVox- and VoxBox-compatible versions.
Nothing about it is specific to the PDK: it works on any module, so it's also the quickest way to bring a hand-maintained module onto the DevKit toolchain.
If the module has no `metadata.json`, or has one that's missing required keys, `jig convert` creates or repairs it first; see [the Jig page](jig.html#migrate-an-existing-module-to-jig) for the details.

```console
~/demo git:(main) git status
On branch main
nothing to commit, working tree clean

~/demo git:(main) jig convert
created /home/user/demo/jig.toml
removed /home/user/demo/Gemfile.lock
warning: PDK-era files no longer used by jig/voxbox, safe to remove: .sync.yml, .pdkignore
convert successful: Gemfile, Rakefile, spec/spec_helper.rb

~/demo git:(main) ✗ git status
Expand All @@ -31,6 +36,10 @@ Changes not staged for commit:
modified: Rakefile
modified: spec/spec_helper.rb

Untracked files:
(use "git add <file>..." to include in what will be committed)
jig.toml

no changes added to commit (use "git add" and/or "git commit -a")
~/demo git:(main) ✗
```
Expand All @@ -53,13 +62,16 @@ Because Jig does not attempt to hide the Bundler environment from you, it will s
| `pdk build` | `jig build` | |
| `pdk release` | `jig release` | |
| `pdk convert` | `jig convert` | |
| `pdk update` | `jig update`* | `bundle exec msync update`* |
| `pdk validate` | `jig validate` | `bundle exec rake validate lint` |
| `pdk update` | `jig msync update`* | `bundle exec msync update`* |
| `pdk validate` | `jig validate` | `bundle exec rake validate lint rubocop` |
| `pdk test unit` | `jig test unit` | `bundle exec rake spec` |

`jig convert` was added in Jig 1.5.0
{: .info }
`jig validate` runs all three checks and stops at the first failure.
Pass `-s`, `-l`, or `-r` to run only the syntax, lint, or rubocop check; for example, `jig validate -sl` skips rubocop.

{% include alert.html type="note" title="*NOTE" content="`pdk update` operates in context of a single module. In contrast, the replacement ModuleSync commands (`jig msync update` and `bundle exec msync update`) should be run in the template repository to push updates to all your modules at once. [Read more](modulesync.html)." %}

{% include alert.html type="note" title="*NOTE" content="`pdk update` operates in context of a single module. In contrast, the replacement ModuleSync commands (`jig update` and `bundle exec msync update`) should be run in the template repository to push updates to all your modules at once. [Read more](modulesync.html)." %}
If you used `jig update` with Jig 1.x, it was renamed to `jig msync update` in Jig 2.0 to make clear that it runs ModuleSync rather than anything Jig-native.
For Jig's own template-driven refresh of a single module, see [`jig renew`](jig.html#refreshing-a-module-from-its-templates).

Browse through the individual subpages of this Developer Tooling section to learn more about each component.
33 changes: 32 additions & 1 deletion docs/_ecosystem_8x/devkit/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,35 @@ built /Users/ben.ford/Projects/demo/pkg/binford2k-demo-0.1.0.tar.gz
Now browse to the [Forge upload page](https://forge.puppet.com/upload), choose the generated file and upload it.
This will create the module listing if required and add a module release to it.

### What gets packaged

Jig validates `metadata.json` before building; errors abort the build and warnings are printed.

By default the package contains only the files the [Puppet module specification](https://github.com/puppetlabs/puppet-specifications/pull/157) allows in a published module: `manifests/`, `lib/`, `data/`, `metadata.json`, and so on.
Development files like `Gemfile`, `spec/`, and dotfiles stay out with no configuration at all.

Jig does not read `.pdkignore` or `.pmtignore`.
If you migrated from the PDK, `jig build` will warn about any leftover ignore file and suggest removing it; for most modules that's all you need to do, since the allowlist already excludes what those files used to exclude.
`.gitignore` is left alone because it belongs to git, not the build.

To ship a file the specification doesn't know about, or to get the old "everything except" behavior back, add a `[build]` section to the module's `jig.toml`:

```toml
# Extend the allowlist with extra files (recommended)
[build]
action = "deny"
exceptions = ["/mycustomfile.txt"]
```

```toml
# Or package everything except the listed paths, the way .pdkignore used to
[build]
action = "allow"
exceptions = ["/spec/**", "/Gemfile"]
```

In both modes `pkg/`, `.git/`, `jig.toml` itself, and `.gitkeep` markers are never packaged.

## Pushing a release from the command line

If you'd like to streamline your workflow, you can push a release directly using Jig.
Expand All @@ -43,7 +72,9 @@ Now that it's configured, you can publish a new version of your module.
jig release --version x.y.z
```

This will update the `metadata.json`, build the package, and then publish it.
This validates the metadata, writes the new version into `metadata.json`, builds the package, and then publishes it.
The version must be plain semver (`MAJOR.MINOR.PATCH`).
Pass `--token` to supply the Forge token on the command line instead of from the config file, and `--skip-validation`, `--skip-build`, or `--skip-publish` to leave out a step; with `--skip-build`, Jig expects the archive to already exist under `pkg/`.

## Scripting a release

Expand Down
Loading