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.
# 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:
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.
# 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_forceis alwaystrue— files are overwritten on every install- No
update.pysupport — upgrades useinit.pywith 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.pyfor 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.
# 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.
- Build Gear — overview of distribution options
- Image Labels — when the
gsf.packagelabel is required
Example Dockerfile
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:
- Create a dev branch in your gear repository
- Build and push the gear image with a dev tag (keep it in sync with
package.yaml) - Test installation on a throwaway target branch using
gsf install - Verify that templates, CI/CD variables, and webhooks are created correctly
- Test update by installing an older version first, then installing the new version
- Test rollback by installing a newer version first, then installing the older version
- Test uninstall to confirm clean removal
- 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 |