Skip to content

voxpupuli/jig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

jig

A Go-based reimplementation of the Puppet Development Kit (PDK), built to be fast, self-contained, and free of Ruby runtime dependencies.

Why jig?

PDK has been an essential tool for Puppet module authors for years. When Perforce moved PDK to a closed-source model, it created a real problem for teams and individuals who depend on open tooling for their workflows. On top of that, PDK carries a heavy Ruby runtime footprint, which adds friction to CI environments and developer machines alike.

jig aims to replace the parts of PDK that matter most: scaffolding new modules, building module packages, and cutting releases. It ships as a single static binary with no external runtime required.

Status

jig is under active development. The table below reflects the current state of planned functionality.

Command Subcommand Status
new module ✅ Working
new class ✅ Working
new defined_type ✅ Working
new fact ✅ Working
new function ✅ Working
new provider ✅ Working
new task ✅ Working
new test ✅ Working
new transport ✅ Working
--skip-interview ✅ Working
Template override ✅ Working
templates dump ✅ Working
templates resolve 🔲 Planned
build ✅ Working
release ✅ Working
validate ✅ Working
test unit ✅ Working
update ✅ Working

Installation

Build from source

Requires Go 1.21 or later.

git clone https://github.com/avitacco/jig.git
cd jig
go build -o jig .

Move the resulting binary somewhere in your $PATH:

mv jig /usr/local/bin/

No other dependencies or runtimes needed.

Usage

jig new module

Scaffolds a new Puppet module with the standard directory structure and metadata.

jig new module <n> [flags]

jig will walk you through an interactive interview to collect module metadata. Values from your config file are used as defaults. If no config is present, jig falls back to your system username and full name.

Flags:

Flag Description
-u, --forge-user Your Puppet Forge username
-a, --author Your full name
-l, --license License type (default: from config, then Apache-2.0)
-s, --summary One-line module summary
-S, --source Source URL for the module
-f, --force Overwrite an existing module directory. The existing directory is backed up with a timestamp before any files are written.
-i, --skip-interview Skip the interactive interview and use flag values or defaults.

jig new class

Generates a new Puppet class manifest and its rspec-puppet spec file inside the current module directory.

jig new class <n>

The class name follows standard Puppet naming conventions. Namespaced names like foo::bar are supported and will generate the correct directory structure under manifests/. The module name prefix must not be included in the name.

jig new defined_type

Generates a new Puppet defined type manifest and its rspec-puppet spec file inside the current module directory.

jig new defined_type <n>

Defined type names follow the same conventions as class names. Namespaced names like foo::bar are supported and will generate the correct directory structure under manifests/. The module name prefix must not be included in the name.

jig new fact

Generates a new custom Facter fact and its spec file inside the current module directory.

jig new fact <n>

Fact names may not contain ::. The generated fact is placed in lib/facter/<name>.rb and its spec in spec/unit/facter/<name>_spec.rb.

jig new function

Generates a new Puppet language function and its spec file inside the current module directory.

jig new function <n>

Function names follow standard Puppet naming conventions. The module name is automatically prepended to form the fully qualified function name (<module>::<name>). The generated function is placed in functions/<name>.pp and its spec in spec/functions/<name>_spec.rb.

jig new provider

Generates a new Puppet resource type and provider using the Resource API, along with spec files for both, inside the current module directory.

jig new provider <n>

Provider names must start with a lowercase letter and contain only lowercase letters, numbers, and underscores ([a-z][a-z0-9_]*). Four files are generated:

  • lib/puppet/type/<name>.rb — the Resource API type definition
  • lib/puppet/provider/<name>/<name>.rb — the Resource API simple provider
  • spec/unit/puppet/type/<name>_spec.rb — spec file for the type
  • spec/unit/puppet/provider/<name>/<name>_spec.rb — spec file for the provider

jig new task

Generates a new Puppet task and its metadata file inside the current module directory.

jig new task <n>

Task names must start with a lowercase letter and contain only lowercase letters, numbers, and underscores ([a-z][a-z0-9_]*). The special name init is valid and maps the task to the module itself. Namespaced names using :: are not valid for tasks.

jig new test

Generates a unit test for an existing class or defined type inside the current module directory.

jig new test <n>

jig looks up the named resource by finding its manifest under manifests/ and inspects the file to determine whether it contains a class or a defined type. The spec file is written to spec/classes/ for classes or spec/defines/ for defined types. The name follows the same conventions as jig new class and jig new defined_type -- namespaced names like foo::bar are supported, and the module name prefix must not be included.

An error is returned if the manifest does not exist, if no matching class or defined type declaration is found in the file, or if a spec file for the named resource already exists.

jig new transport

Generates a new Puppet Resource API transport and its associated files inside the current module directory.

jig new transport <n>

Transport names must start with a lowercase letter and contain only lowercase letters, numbers, and underscores ([a-z][a-z0-9_]*). Snake case names like my_device are valid and will be converted to PascalCase (MyDevice) where required by Ruby class naming conventions. Five files are generated:

  • lib/puppet/transport/<n>.rb — the transport implementation
  • lib/puppet/transport/schema/<n>.rb — the Resource API transport schema
  • lib/puppet/util/network_device/<n>/device.rb — legacy device compatibility shim
  • spec/unit/puppet/transport/<n>_spec.rb — spec file for the transport
  • spec/unit/puppet/transport/schema/<n>_spec.rb — spec file for the schema

Flags on jig new:

The following flag is available on all jig new subcommands:

Flag Description
-t, --template-dir Path to a custom template directory. See Template Overrides below.

Global flags:

Flag Description
--config Path to config file
--debug Enable debug output

Module naming: jig validates module names against Puppet's naming conventions. Violations produce a warning but do not stop scaffolding.

jig templates dump

Extracts all embedded default templates to a directory on disk. This is useful as a starting point for creating your own custom templates. If the destination directory already exists it will be renamed with a timestamp suffix before writing.

jig templates dump <destination>

For example:

jig templates dump ~/.config/jig/templates

You can then edit the files in the destination directory and point jig at them using --template-dir or the template_dir config key.

jig build

Builds a module package suitable for uploading to the Puppet Forge. The package is written to pkg/<forge-user>-<module>-<version>.tar.gz relative to the current directory.

jig build

Metadata validation runs before the build. Errors abort the build; warnings are printed and execution continues.

jig release

Validates metadata, sets the version, builds the module package, and publishes it to the Puppet Forge.

jig release [flags]

The release sequence is:

  1. Validate the version string and module metadata (unless --skip-validation).
  2. Write the new version into metadata.json.
  3. Build the module package (unless --skip-build).
  4. Upload the package to the Forge (unless --skip-publish).

A Forge API token is required for publishing. Set forge_token in your config file or pass it via --token. You can generate a token from your account page on the Puppet Forge.

Flags:

Flag Description
-v, --version Version to release, e.g. 1.2.3 (required)
-k, --token Forge API token (overrides forge_token in config)
--skip-validation Skip metadata validation
--skip-build Skip building the module archive
--skip-publish Skip publishing to the Forge

If --skip-build is set without --skip-publish, jig expects the archive to already exist under pkg/. An error is returned if it is not found.

jig validate

Runs validation and linting against the current module. This is a passthrough command that shells out to bundle exec rake validate lint, so it requires a Ruby toolchain and the module's bundled gems to be installed.

jig validate [args...]

Any additional arguments are passed through verbatim to the underlying rake invocation.

jig test unit

Runs the module's unit tests. This is a passthrough command that shells out to bundle exec rake spec, so it requires a Ruby toolchain and the module's bundled gems to be installed.

jig test unit [args...]

Any additional arguments are passed through verbatim to the underlying rake invocation.

jig update

Synchronises the module's managed files from the module's templates. This is a passthrough command that shells out to bundle exec msync update, so it requires a Ruby toolchain and the module's bundled gems to be installed.

jig update [args...]

Any additional arguments are passed through verbatim to the underlying msync invocation.

Template Overrides

jig embeds default templates for all generated files. If you want to customise them, you can point jig at a directory of your own templates. Any template found in your custom directory takes precedence over the embedded default. Templates not present in your custom directory fall back to the embedded defaults automatically, so you only need to include the files you want to change.

The easiest way to get started is to run jig templates dump to extract the default templates, then edit the ones you want to change.

Template directory structure

Your custom template directory must mirror the structure of jig's embedded templates:

templates/
  common/
    gitkeep
  module/
    manifests/
      init.pp
    spec/
      class_spec.rb
      spec_helper.rb
      default_facts.yml
    Gemfile
    Rakefile
    README.md
    CHANGELOG.md
    gitignore
    pdkignore
    rubocop.yml
    hiera.yaml
  class/
    class.pp
    class_spec.rb
  type/
    defined_type.pp
    defined_type_spec.rb
  fact/
    fact.rb
    fact_spec.rb
  function/
    function.pp
    function_spec.rb
  provider/
    type.rb
    type_spec.rb
    provider.rb
    provider_spec.rb
  task/
    task.sh
    metadata.json
  transport/
    transport.rb
    transport_spec.rb
    device.rb
    schema/
      schema.rb
      schema_spec.rb

Configuring the template directory

There are three ways to tell jig where your custom templates live, in order of precedence:

Command line flag:

jig new --template-dir /path/to/templates module mymodule

Environment variable:

export JIG_TEMPLATE_DIR=/path/to/templates
jig new module mymodule

Config file (~/.config/jig/config.toml):

template_dir = "/path/to/templates"

Configuration

jig looks for a config file at ~/.config/jig/config.toml. All fields are optional. If the file does not exist, jig falls back to sensible defaults.

forge_username = "avitacco"
author         = "John Doe"
license        = "Apache-2.0"
forge_token    = "your-forge-token"
template_dir   = "/path/to/templates"

The config path can be overridden with the --config flag or the JIG_CONFIG environment variable.

Contributing

Contributions are welcome. The project is in early stages, so the best place to start is by opening an issue to discuss what you want to work on before sending a PR.

Project layout

.
├── main.go
├── commands/        # Cobra command definitions
└── internal/
    ├── build/
    ├── config/
    ├── forge/
    ├── module/      # Module metadata and validation
    ├── release/
    ├── scaffold/    # Scaffolding orchestration
    └── template/   # Template rendering with fallback logic
        └── templates/  # Embedded default templates

Testing

Run the full test suite with:

go test ./...

Tests live alongside the source files they cover (*_test.go), which is the standard Go convention. The commands/ and internal/config/ packages do not currently have tests -- the former is thin Cobra wiring and the latter is thin Viper wiring, so the internal packages are where the meaningful coverage lives.

A few patterns used throughout the test suite that contributors should follow:

  • Table-driven tests for functions with multiple input variations. Use a cases := []struct{...} slice and t.Run for each case.
  • t.TempDir() for any test that touches the filesystem. It is cleaned up automatically after the test and requires no defer os.Remove.
  • fakeRenderer in internal/scaffold implements the scaffold.Renderer interface and can be used to test template rendering paths without hitting the real embedded templates.
  • makeBuildDir in internal/build, makeModuleDir in internal/scaffold, and makeModuleDir in internal/release are shared helpers that create realistic on-disk module structures for tests that need them. fakePublisher in internal/release implements the forge.Publisher interface for testing the release sequence without making real HTTP calls.
  • Both characterization tests (pinning current behavior) and adversarial tests (checking rejection of invalid or malicious input) are expected. When adding a new feature, include both.

Design notes for contributors

  • Templates are embedded via go:embed. External templates take precedence over embedded ones, with per-file fallback to embedded defaults when a custom template is not found. Template names are validated to prevent path traversal before any file is read.
  • --force never deletes existing files outright. It creates a timestamped backup of the target directory first.
  • Module name validation uses a ValidationResult type with an iota-based Severity. Violations at the Warning level do not halt execution. Version strings must be valid semver (MAJOR.MINOR.PATCH). URL fields (source, project_page, issues_url) must use http or https schemes when present; invalid URLs are errors that abort the build and release.
  • The Forge HTTP client (internal/forge) is hidden behind a Publisher interface so the release sequence can be tested without making real network requests.
  • Component names (module names, class names, defined type names) are validated to reject empty strings, path separators, and traversal sequences before they are used to construct filesystem paths.
  • os.Getwd() is called only in the commands/ layer. Internal packages receive directory paths as arguments, which keeps them testable without manipulating the process working directory.
  • Config is handled with Viper.

NOTICE

Some default template files included in this project are derived from the pdk-templates project, copyright Puppet Labs, and are used under the terms of the Apache License, Version 2.0.

License

See LICENSE.

About

A tool for building and publishing Puppet modules

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages