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
16 changes: 16 additions & 0 deletions .agents/scripts/agent-environment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

oldstate=$(set +o)

set -eu
set -o pipefail

# Resolve the Kayobe checkout root and set the default agent workdir beneath it.

PARENT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AGENTS_ROOT="$(dirname "${PARENT}")"
REPO_ROOT="$(dirname "${AGENTS_ROOT}")"

export KAYOBE_AGENT_WORKDIR="${KAYOBE_AGENT_WORKDIR:-${REPO_ROOT}/.agents/workdir}"

eval "$oldstate"
151 changes: 151 additions & 0 deletions .agents/skills/add-cli-command/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
name: add-cli-command
description: "Add a new Kayobe CLI command end-to-end. Use when: adding a CLI subcommand, creating a new kayobe command, wiring up a playbook to the CLI. Touches commands.py, setup.cfg, playbook, and unit test."
---

# Add a Kayobe CLI Command

## When to Use

- Adding a new `kayobe <noun> <verb>` command
- Wiring an existing or new Ansible playbook into the CLI

## Procedure

### 1. Define the command class in `kayobe/cli/commands.py`

Choose the right mixin combination:

| If the command runs... | Inherit from |
|---|---|
| Only Kayobe playbooks | `KayobeAnsibleMixin, VaultMixin, Command` |
| Kayobe + Kolla Ansible | `KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, Command` |

Pattern for Kayobe-only commands:

```python
class MyNewCommand(KayobeAnsibleMixin, VaultMixin, Command):
"""Short description of the command."""

def get_parser(self, prog_name):
parser = super(MyNewCommand, self).get_parser(prog_name)
# Add command-specific arguments here if needed
return parser

def take_action(self, parsed_args):
self.app.LOG.debug("Running my new command")
playbooks = _build_playbook_list("my-new-playbook")
self.run_kayobe_playbooks(parsed_args, playbooks,
limit="target-group")
```

Pattern for commands that also invoke Kolla Ansible (inherit `KollaAnsibleMixin`):

```python
class MyNewKollaCommand(KollaAnsibleMixin, KayobeAnsibleMixin, VaultMixin, Command):
"""Short description of the command."""

def get_parser(self, prog_name):
parser = super(MyNewKollaCommand, self).get_parser(prog_name)
return parser

def take_action(self, parsed_args):
self.app.LOG.debug("Running my new kolla command")
self.handle_kolla_tags_limits_deprecation(parsed_args)
playbooks = _build_playbook_list("my-new-playbook")
self.run_kayobe_playbooks(parsed_args, playbooks)
# Run kolla-ansible steps as needed, e.g.:
# self.run_kolla_ansible_overcloud(parsed_args, "deploy")
```

`handle_kolla_tags_limits_deprecation(parsed_args)` **must** be called first in
every `KollaAnsibleMixin` command. It enforces warnings and mutual-exclusion
checks for `--kolla-limit`, `--kolla-tags`, and `--kolla-skip-tags`.

Key helpers:
- `_build_playbook_list("name1", "name2")` builds absolute paths to `ansible/<name>.yml`
- `_get_playbook_path("name")` returns a single playbook path
- Use `ignore_limit=True` for plays that must always run against all hosts (e.g. localhost config generation)
- Use `extra_vars={}` for runtime overrides
- Use `tags=` and `check=False` when needed

### 2. Register entry points in `setup.cfg`

Add **two** entries:

```ini
# Under [entry_points] > kayobe.cli=
my_new_command = kayobe.cli.commands:MyNewCommand

# Under kayobe.cli.my_new_command =
kayobe.cli.my_new_command =
hooks = kayobe.cli.commands:HookDispatcher
```

The entry point name uses underscores and maps to the CLI as `kayobe my new command`.

### 3. Create the Ansible playbook

Create `ansible/my-new-playbook.yml` using kebab-case:

```yaml
---
- name: Description of what this play does
hosts: target-group
tags:
- my-new-playbook
roles:
- role: my-role
```

Keep the playbook minimal. Put logic in a role under `ansible/roles/`.

### 4. Add a unit test in `kayobe/tests/unit/cli/test_commands.py`

```python
@mock.patch.object(commands.KayobeAnsibleMixin,
"run_kayobe_playbooks")
def test_my_new_command(self, mock_run):
command = commands.MyNewCommand(TestApp(), [])
parser = command.get_parser("test")
parsed_args = parser.parse_args([])
result = command.run(parsed_args)
self.assertEqual(0, result)
expected_calls = [
mock.call(
mock.ANY,
[utils.get_data_files_path("ansible", "my-new-playbook.yml")],
limit="target-group",
),
]
self.assertListEqual(expected_calls, mock_run.call_args_list)
```

### 5. Add a release note

```bash
tox -e venv -- reno new add-my-new-command
```

Use the `features` section.

### 6. Update documentation

Add or update the relevant section in `doc/source/`.

### 7. Validate

```bash
tox -e py3 # unit tests pass
tox -e pep8 # style checks pass
```

## Checklist

- [ ] Command class in `kayobe/cli/commands.py` with correct mixins
- [ ] Two `setup.cfg` entry points (command + hook dispatcher)
- [ ] Ansible playbook in `ansible/` (kebab-case)
- [ ] Unit test in `kayobe/tests/unit/cli/test_commands.py`
- [ ] Release note via reno
- [ ] Documentation updated
- [ ] `tox -e py3` and `tox -e pep8` pass
134 changes: 134 additions & 0 deletions .agents/skills/add-config-variable/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
name: add-config-variable
description: "Add a new Kayobe configuration variable with proper inventory defaults, operator examples, and docs. Use when: adding a config knob, introducing a new setting, creating a new component config file. Touches group_vars, etc/kayobe, and doc/source."
---

# Add a Kayobe Configuration Variable

## When to Use

- Adding a new user-configurable setting
- Introducing a variable for a new or existing component
- Renaming or restructuring existing configuration

## Procedure

### 1. Add the inventory default

Add the variable to the appropriate file under `ansible/inventory/group_vars/`.

- Global defaults go in `ansible/inventory/group_vars/all/<component>` (extensionless file).
- Group-specific defaults go in `ansible/inventory/group_vars/<group>/<component>`.

**For scalar variables:**

```yaml
# Description of what this variable controls. Default is <value>.
my_component_setting: "default_value"
```

**For list variables, use the three-tier pattern:**

```yaml
# Combined list of items (do not override directly).
my_component_items: >
{{ my_component_items_default +
my_component_items_extra }}

# List of default items.
my_component_items_default:
- item1
- item2

# List of extra items added by the operator.
my_component_items_extra: []
```

This lets operators extend defaults via `_extra` without replacing the whole list.

### 2. Add the commented example in `etc/kayobe/`

**Only do this for variables that operators are expected to override directly.**
Skip this step for internal defaults — those should remain inventory-only.

Add a commented-out version in the appropriate `etc/kayobe/` file:

```yaml
# Description of what this variable controls. Default is <value>.
#my_component_setting:
```

For three-tier lists, document all three variables:

```yaml
# Combined list of items.
#my_component_items:

# List of default items.
#my_component_items_default:

# List of extra items added by the operator.
#my_component_items_extra:
```

Rules:
- Keep the same comment text as the inventory default (adjusted for operator audience).
- Preserve the `###` section dividers and comment style used in the rest of the file.
- The file name in `etc/kayobe/` should follow the file alignment reference.

### 3. Update documentation

Add or update the variable description in the existing authoritative doc page
for the component. Search `doc/source/` to find the right file — do not assume
a filename from the component name. For example:

- Kolla-ansible settings → `doc/source/configuration/reference/kolla-ansible.rst`
- Kolla settings → `kolla.rst`
- OpenStack/kayobe settings → `doc/source/configuration/reference/kayobe.rst`
- Host-group settings (firewall, sysctl, etc.) → `doc/source/configuration/reference/hosts.rst`
- IPA → `doc/source/configuration/reference/ironic-python-agent.rst`

When in doubt, `grep -r 'nearby_variable_name' doc/source/` to locate the right page.

### 4. Add a release note

```bash
tox -e venv -- reno new add-my-component-setting
```

Use the `features` section for new variables, `upgrade` if existing behavior changes.

### 5. Validate

```bash
tox -e pep8 # yamllint checks etc/kayobe/
tox -e ansible-lint # lint the inventory and playbooks
```

## File Alignment Reference

| Inventory defaults | Operator examples | Docs |
|---|---|---|
| `ansible/inventory/group_vars/all/<component>` | `etc/kayobe/<component>.yml` | Find by searching `doc/source/` |
| `ansible/inventory/group_vars/<group>/<component>` | `etc/kayobe/<group>.yml` | Same docs file |

Note: for group-scoped variables the `etc/kayobe/` file is named after the
**group** (e.g. `compute`, `controllers`), not the component file inside
that group. For example `ansible/inventory/group_vars/compute/firewall`
corresponds to `etc/kayobe/compute.yml`.

## Naming Conventions

- Prefix variables with the component name: `kolla_`, `compute_`, `seed_`, `dns_`, etc.
- Use `snake_case` for all variable names.
- Use `_default` and `_extra` suffixes for the three-tier list pattern.
- Use `_enabled` (bool) for feature toggles.

## Checklist

- [ ] Variable added in `ansible/inventory/group_vars/` with comment and default value
- [ ] Commented example added in `etc/kayobe/<component>.yml`
- [ ] Documentation updated in `doc/source/`
- [ ] Release note via reno
- [ ] `tox -e pep8` passes (yamllint)
- [ ] `tox -e ansible-lint` passes (if touching playbooks or roles)
Loading