Files
Jack Carter 2041cd2d89 fix: preserve existing field values on partial updates
Changes module behavior so that omitting auto_groups, peers, or
similar list fields on update preserves the existing values instead
of wiping them to [].

Modules changed:
- netbird_setup_key: auto_groups default [] -> None, preserve on update
- netbird_group: peers default [] -> None, preserve on update,
  normalize peer dicts to IDs
- netbird_user: auto_groups default [] -> None, preserve on update

Also:
- Reimplement get_current_user() since /api/users/me does not exist.
  Now lists users and matches by is_current flag, with explicit error
  on multi-user deployments without the flag.
- Configure role: use default(omit) for setup key auto_groups so
  the module-level preservation works through the role.
- Configure role: conditional auto_groups resolution (skip when
  auto_groups not defined in YAML config).
- Configure role: DNS zone distribution_groups now handles both
  group names and raw IDs (falls back to original value when not
  found in group_ids map).
- Updated DOCUMENTATION strings to remove stale default: [] and
  document preservation behavior.
2026-03-30 21:21:28 +02:00

329 lines
9.5 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2024, Community
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Ansible module for managing NetBird setup keys."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: netbird_setup_key
short_description: Manage NetBird setup keys
description:
- Create, update, and delete setup keys in NetBird.
- Setup keys are used to register new peers to the network.
version_added: "1.0.0"
author:
- Community
options:
state:
description:
- The desired state of the setup key.
type: str
choices: ['present', 'absent']
default: present
key_id:
description:
- The unique identifier of the setup key.
- Required when state is absent or when updating by ID.
type: str
name:
description:
- Name of the setup key.
- Required when creating a new setup key.
type: str
key_type:
description:
- Type of the setup key.
- 'one-off' keys can only be used once.
- 'reusable' keys can be used multiple times.
type: str
choices: ['one-off', 'reusable']
default: one-off
expires_in:
description:
- Expiration time in seconds.
- Default is 86400 (24 hours).
type: int
default: 86400
revoked:
description:
- Whether the key is revoked.
type: bool
default: false
auto_groups:
description:
- List of group IDs to auto-assign to peers registered with this key.
- When updating an existing key and this is not specified, the current auto_groups are preserved.
type: list
elements: str
usage_limit:
description:
- Maximum number of times the key can be used.
- 0 means unlimited (for reusable keys).
type: int
default: 0
ephemeral:
description:
- Whether peers registered with this key are ephemeral.
- Ephemeral peers are automatically removed when disconnected.
type: bool
default: false
allow_extra_dns_labels:
description:
- Allow extra DNS labels for peers registered with this key.
type: bool
default: false
extends_documentation_fragment:
- community.ansible_netbird.netbird
requirements:
- python >= 3.6
'''
EXAMPLES = r'''
- name: Create a one-off setup key
community.ansible_netbird.netbird_setup_key:
api_url: "https://netbird.example.com"
api_token: "{{ netbird_token }}"
name: "new-server-key"
key_type: "one-off"
expires_in: 3600
state: present
register: setup_key
- name: Create a reusable setup key with auto groups
community.ansible_netbird.netbird_setup_key:
api_url: "https://netbird.example.com"
api_token: "{{ netbird_token }}"
name: "developer-machines"
key_type: "reusable"
expires_in: 604800
auto_groups:
- "developers-group-id"
state: present
- name: Create an ephemeral setup key
community.ansible_netbird.netbird_setup_key:
api_url: "https://netbird.example.com"
api_token: "{{ netbird_token }}"
name: "temporary-access"
key_type: "reusable"
ephemeral: true
state: present
- name: Revoke a setup key
community.ansible_netbird.netbird_setup_key:
api_url: "https://netbird.example.com"
api_token: "{{ netbird_token }}"
key_id: "key-id-123"
revoked: true
state: present
- name: Delete a setup key
community.ansible_netbird.netbird_setup_key:
api_url: "https://netbird.example.com"
api_token: "{{ netbird_token }}"
key_id: "key-id-123"
state: absent
'''
RETURN = r'''
setup_key:
description: The setup key object.
returned: success
type: dict
contains:
id:
description: Setup key ID.
type: str
key:
description: The actual setup key value (only returned on creation).
type: str
name:
description: Setup key name.
type: str
type:
description: Key type (one-off or reusable).
type: str
expires:
description: Expiration timestamp.
type: str
revoked:
description: Whether the key is revoked.
type: bool
auto_groups:
description: Auto-assigned group IDs.
type: list
usage_limit:
description: Usage limit.
type: int
used_times:
description: Number of times the key has been used.
type: int
last_used:
description: Last used timestamp.
type: str
ephemeral:
description: Whether key creates ephemeral peers.
type: bool
valid:
description: Whether the key is still valid.
type: bool
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.ansible_netbird.plugins.module_utils.netbird_api import (
NetBirdAPI,
NetBirdAPIError,
extract_ids,
netbird_argument_spec
)
def find_setup_key_by_name(api, name):
"""Find a setup key by name."""
keys, _ = api.list_setup_keys()
for key in (keys or []):
if key.get('name') == name:
return key
return None
def setup_key_needs_update(current, params):
"""Check if setup key needs to be updated."""
if params.get('name') is not None and current.get('name') != params['name']:
return True
if params.get('revoked') is not None and current.get('revoked') != params['revoked']:
return True
if params.get('auto_groups') is not None:
current_groups = set(extract_ids(current.get('auto_groups') or []))
desired_groups = set(extract_ids(params['auto_groups'] or []))
if current_groups != desired_groups:
return True
return False
def run_module():
"""Main module execution."""
argument_spec = netbird_argument_spec()
argument_spec.update(
state=dict(type='str', choices=['present', 'absent'], default='present'),
key_id=dict(type='str'),
name=dict(type='str'),
key_type=dict(type='str', choices=['one-off', 'reusable'], default='one-off'),
expires_in=dict(type='int', default=86400),
revoked=dict(type='bool', default=False),
auto_groups=dict(type='list', elements='str'),
usage_limit=dict(type='int', default=0),
ephemeral=dict(type='bool', default=False),
allow_extra_dns_labels=dict(type='bool', default=False)
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=[
('key_id', 'name'),
]
)
api = NetBirdAPI(
module,
module.params['api_url'],
module.params['api_token'],
module.params['validate_certs']
)
state = module.params['state']
key_id = module.params['key_id']
name = module.params['name']
result = dict(
changed=False,
setup_key={}
)
try:
# Find existing setup key
existing_key = None
if key_id:
try:
existing_key, _ = api.get_setup_key(key_id)
except NetBirdAPIError as e:
if e.status_code != 404:
raise
elif name:
existing_key = find_setup_key_by_name(api, name)
if state == 'absent':
if existing_key:
if not module.check_mode:
api.delete_setup_key(existing_key['id'])
result['changed'] = True
result['msg'] = 'Setup key deleted successfully'
module.exit_json(**result)
# state == 'present'
if existing_key:
# Use existing values as fallback for fields the user didn't specify
effective_auto_groups = module.params['auto_groups']
if effective_auto_groups is None:
effective_auto_groups = existing_key.get('auto_groups', [])
# Check if update is needed
update_params = {
'name': name,
'revoked': module.params['revoked'],
'auto_groups': effective_auto_groups
}
if setup_key_needs_update(existing_key, update_params):
if not module.check_mode:
key, _ = api.update_setup_key(
existing_key['id'],
revoked=module.params['revoked'],
auto_groups=effective_auto_groups
)
result['setup_key'] = key
else:
result['setup_key'] = existing_key
result['changed'] = True
else:
result['setup_key'] = existing_key
else:
# Create new setup key
if not name:
module.fail_json(msg="name is required when creating a new setup key")
if not module.check_mode:
key, _ = api.create_setup_key(
name=name,
key_type=module.params['key_type'],
expires_in=module.params['expires_in'],
revoked=module.params['revoked'],
auto_groups=module.params['auto_groups'] or [],
usage_limit=module.params['usage_limit'],
ephemeral=module.params['ephemeral'],
allow_extra_dns_labels=module.params['allow_extra_dns_labels']
)
result['setup_key'] = key
result['changed'] = True
module.exit_json(**result)
except NetBirdAPIError as e:
module.fail_json(msg=str(e), status_code=e.status_code, response=e.response)
def main():
run_module()
if __name__ == '__main__':
main()