Skip to content

Gear Development Cases

This guide covers common cases and best practices when developing and distributing a Gear.

Version synchronization between image tag and package.yaml

When publishing a Gear as a Docker image, the image tag should match the version field in package.yaml.

# package.yaml
apiVersion: v1
type: gitlab-ci-pipeline
name: my-gear
version: 1.0.0
# Build and push with matching tag
docker build -t registry.example.com/team/my-gear:1.0.0 .
docker push registry.example.com/team/my-gear:1.0.0

If the image tag does not exactly match the version in package.yaml, git-system-follower will log a warning:

Mismatch found in version of gear (1.0.0_gsf_image) and package.yaml (1.0.0)

This is not advisable. A mismatch can cause confusion during rollback and version tracking. Always keep the image tag in sync with package.yaml.

Name synchronization between image and package.yaml

The name field in package.yaml should match the gear image name. git-system-follower uses this to verify that the correct gear is being installed.

# package.yaml
apiVersion: v1
type: gitlab-ci-pipeline
name: my-gear
version: 1.0.0
# Image name should match the name in package.yaml
docker build -t registry.example.com/team/my-gear:1.0.0 .

If the image name does not contain the package name, git-system-follower will raise an error to prevent incorrect gears from being installed.

Gear structure types

git-system-follower auto-detects the gear structure type by examining the scripts/ directory layout.

Simple (single version)

A simple gear has Python scripts directly in scripts/ without version subdirectories.

git-system-follower-package/
  package.yaml
  scripts/
    init.py
    delete.py
    templates/
      my-template/
        cookiecutter.json
        {{ cookiecutter.gsf_repository_name }}/
          ...
    files/
      my-template/
        static.txt

Static (non-templated) files can also be placed in scripts/files/<template>/ and will be copied as-is to the target repository. See Static files below for full behavior details.

When to use: Choose simple when your gear is self-contained, doesn't need versioned migrations, and will be updated by reinstalling with --force.

Key characteristics:

  • is_force is always true — files are overwritten on every install
  • No update.py support — upgrades use init.py with force
  • Best for small, self-contained gears

Complex (multiple versions)

A complex gear has version-named subdirectories under scripts/.

git-system-follower-package/
  package.yaml
  scripts/
    1.0.0/
      init.py
      update.py
      delete.py
      templates/
        my-template/
          ...
      files/
        my-template/
          static.txt
    1.1.0/
      init.py
      update.py
      delete.py
      templates/
        my-template/
          ...
      files/
        my-template/
          static.txt

Static (non-templated) files can also be placed at scripts/<version>/files/<template>/. See Static files below for full behavior details.

When to use: Choose complex when your gear needs controlled migrations between versions, or when you want to preserve user modifications to files during upgrades.

Key characteristics:

  • Supports update.py for versioned migrations
  • On upgrade, git-system-follower walks through all intermediate versions sequentially
  • Preferred for long-term maintainability

Warning

Simple and complex gears should not be mixed in the same repository. Installing a simple gear on a branch with a complex gear (or vice versa) will exit with an error.

To migrate between gear types, use the --force flag:

gsf install -r https://gitlab.example.com/team/project.git \
            -b main \
            -t <GITLAB_TOKEN> \
            --force \
            registry.example.com/team/my-gear:1.0.0

This allows the structure type change but review the affected files in your repository manually to make sure everything looks as expected before moving forward.

For more details, see Gears and apiVersion list.

Static files

Static files are copied verbatim to the target repository — they bypass cookiecutter rendering entirely. Unlike cookiecutter-managed files, they do not use skip_files and are not stored in the GSF state file.

Use static files when a file must be delivered exactly as-is (binary assets, pre-rendered configs, licenses, etc.) or when you want to ship a file alongside a cookiecutter template without putting it through Jinja.

Placement

Place static files under scripts/files/<template-name>/ (or scripts/<version>/files/<template-name>/ for complex gears). Call create_template / delete_template with the same template name as usual.

scripts/
  init.py
  delete.py
  files/
    my-template/
      .editorconfig
      docs/contributing.md
# init.py
from git_system_follower.develop.api.types import Parameters
from git_system_follower.develop.api.templates import create_template

def main(parameters: Parameters):
    create_template(parameters, 'my-template')

A gear can have both a cookiecutter templates/<name>/ and a files/<name>/ using the same template name — they are independent and both get applied when create_template is called.

Behavior

Operation No user changes User changes found
Fresh install Always write Always write
Update / rollback Overwrite Warn + skip
Uninstall Delete Warn + skip

During update or rollback, GSF uses the previously installed gear's files/ directory as the baseline to detect user changes:

  • No user changes (target matches the previously installed gear file) — GSF overwrites with the new gear version.
  • User changes found (target differs from the previously installed gear file) — GSF leaves the file untouched and logs a warning.

On fresh install the file is always written. On uninstall the current gear's file is compared against the target to decide whether to delete.

Note

User changes in static files are always preserved. Unlike cookiecutter templates, static files are never overwritten when user changes are detected — not by --force, --autoheal, or any other flag. GSF will warn and skip the file regardless of the operation.

package.yaml

A package.yaml supports the following fields:

apiVersion: v1
type: gitlab-ci-pipeline
name: my-gear
version: 1.0.0
dependencies:
  - artifactory.company.com/team/dependency-image:1.0.0
Section Required Description
apiVersion Yes Must be v1
type Yes Gear type (e.g. gitlab-ci-pipeline)
name Yes Unique gear name
version Yes Gear version
dependencies No List of dependency Docker images

For the full field reference, see apiVersion v1.

Develop API

Package scripts should import from the develop API:

from git_system_follower.develop.api.types import Parameters
from git_system_follower.develop.api.templates import create_template, delete_template
from git_system_follower.develop.api.cicd_variables import create_variable, delete_variable
from git_system_follower.develop.api.webhooks import create_webhook, delete_webhook

For the full API reference, see Develop Interface.

Building your Gear

Gears can be distributed as Docker images or OCI artifacts.

Example Dockerfile

FROM scratch

LABEL gsf.package="true"

COPY git-system-follower-package /git-system-follower-package
docker build -t registry.example.com/team/my-gear:1.0.0 .
docker push registry.example.com/team/my-gear:1.0.0

Installing and uninstalling Gears

Install example

gsf install -r https://gitlab.example.com/team/project.git \
            -b main \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.0.0

Uninstall example

gsf uninstall -r https://gitlab.example.com/team/project.git \
              -b main \
              -t <GITLAB_TOKEN> \
              registry.example.com/team/my-gear:1.0.0

Development workflow for a dev branch

When developing a new Gear or iterating on an existing one, follow this workflow:

  1. Create a dev branch in your gear repository
  2. Build and push the gear image with a dev tag (keep it in sync with package.yaml)
  3. Test installation on a throwaway target branch using gsf install
  4. Verify that templates, CI/CD variables, and webhooks are created correctly
  5. Test update by installing an older version first, then installing the new version
  6. Test rollback by installing a newer version first, then installing the older version
  7. Test uninstall to confirm clean removal
  8. Merge to main and push the release image to the registry
# 1. Build dev version
docker build -t registry.example.com/team/my-gear:1.1.0-dev.1 .
docker push registry.example.com/team/my-gear:1.1.0-dev.1

# 2. Test fresh install
gsf install -r https://gitlab.example.com/team/target-project.git \
            -b test-branch \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.1.0-dev.1

# 3. Verify results in GitLab (check files, CI/CD variables, etc.)

# 4. Test update: install an older version, then the new one
gsf install -r https://gitlab.example.com/team/target-project.git \
            -b test-branch \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.0.0
gsf install -r https://gitlab.example.com/team/target-project.git \
            -b test-branch \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.1.0-dev.1

# 5. Test rollback: install a newer version, then downgrade
gsf install -r https://gitlab.example.com/team/target-project.git \
            -b test-branch \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.2.0
gsf install -r https://gitlab.example.com/team/target-project.git \
            -b test-branch \
            -t <GITLAB_TOKEN> \
            registry.example.com/team/my-gear:1.0.0

# 6. Clean up
gsf uninstall -r https://gitlab.example.com/team/target-project.git \
              -b test-branch \
              -t <GITLAB_TOKEN> \
              registry.example.com/team/my-gear:1.0.0

# 7. After merging to main, build and push the release image
docker build -t registry.example.com/team/my-gear:1.1.0 .
docker push registry.example.com/team/my-gear:1.1.0

Tip

Use --debug flag during development to see detailed logs of what git-system-follower is doing.

Token permissions

Different operations require different access levels. See Token Permissions for the full hierarchy.

Role Required for
Developer (30) Push, merge requests, state management
Maintainer (40) CI/CD variable and webhook management