mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-06-09 18:16:09 +00:00
Kubeconfig module improvement (#1123)
* Add kubeconfig module for managing Kubernetes config files * Remove unnecessary requirement & Change version * Move functions to module_utils * Add unit tests * Add kubeconfig module for managing Kubernetes config files * Remove unnecessary requirement & Change version * Move functions to module_utils * Add unit tests * Avoid linter errors * Improve documentation clarity * Redact sensitive kubeconfig information * Imprvoe verbosity * Move import statement for to_native to avoid linters check failure * Fix linting error * Add remove behavior * Add tests for remove behavior * Imporve documentation * Add changelog --------- Co-authored-by: Bianca Henderson <bianca@redhat.com>
This commit is contained in:
2
changelogs/fragments/1123-kubeconfig-remove-behavior.yml
Normal file
2
changelogs/fragments/1123-kubeconfig-remove-behavior.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
minor_changes:
|
||||
- kubeconfig - add ``remove`` value to the ``behavior`` option, allowing entries to be deleted from the kubeconfig file by name (https://github.com/ansible-collections/kubernetes.core/pull/1123).
|
||||
@@ -53,6 +53,8 @@ def merge_by_name(existing, new):
|
||||
if name in merged:
|
||||
if behavior == "keep":
|
||||
continue
|
||||
elif behavior == "remove":
|
||||
del merged[name]
|
||||
elif behavior == "replace":
|
||||
merged[name] = item_copy
|
||||
else:
|
||||
@@ -73,6 +75,8 @@ def merge_by_name(existing, new):
|
||||
result[key] = item_copy[key]
|
||||
merged[name] = result
|
||||
else:
|
||||
if behavior == "remove":
|
||||
continue
|
||||
merged[name] = item_copy
|
||||
|
||||
return list(merged.values())
|
||||
|
||||
@@ -29,6 +29,7 @@ notes:
|
||||
- The default is V(merge), which merges nested C(cluster), C(user), and C(context) data so unspecified keys are preserved.
|
||||
- With V(replace), the previous entry for that name is dropped and only the new definition is used.
|
||||
- With V(keep), the existing entry is left unchanged.
|
||||
- With V(remove), the existing entry is deleted from the kubeconfig entirely. If no entry with that name exists, the operation is silently skipped.
|
||||
- This can be used to move kubeconfig files to a different location with different content.
|
||||
- This module does not validate cluster connectivity or authentication.
|
||||
- The module supports C(check_mode) and will not write files when enabled.
|
||||
@@ -57,7 +58,7 @@ options:
|
||||
- List of cluster definitions to merge into the kubeconfig.
|
||||
- Each cluster is identified by its C(name).
|
||||
- When C(name) matches an existing cluster, the default C(behavior) is V(merge).
|
||||
- See the C(behavior) suboption for V(replace) and V(keep).
|
||||
- See the C(behavior) suboption for V(replace), V(keep), and V(remove).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
@@ -74,14 +75,15 @@ options:
|
||||
- C(merge) - Update only the specified fields, preserve others (default).
|
||||
- C(replace) - Replace the entire cluster definition.
|
||||
- C(keep) - Keep existing cluster, skip this entry.
|
||||
- C(remove) - Remove the cluster entry entirely. Silently skipped if the entry does not exist.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
choices: ['merge', 'replace', 'keep', 'remove']
|
||||
default: merge
|
||||
cluster:
|
||||
description:
|
||||
- Cluster configuration details.
|
||||
- Not required when C(behavior) is V(remove).
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
server:
|
||||
description:
|
||||
@@ -115,7 +117,7 @@ options:
|
||||
- List of user authentication configurations.
|
||||
- Each user is identified by its C(name).
|
||||
- When C(name) matches an existing user, the default C(behavior) is V(merge).
|
||||
- See the C(behavior) suboption for V(replace) and V(keep).
|
||||
- See the C(behavior) suboption for V(replace), V(keep), and V(remove).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
@@ -132,14 +134,15 @@ options:
|
||||
- C(merge) - Update only the specified fields, preserve others (default).
|
||||
- C(replace) - Replace the entire user definition.
|
||||
- C(keep) - Keep existing user, skip this entry.
|
||||
- C(remove) - Remove the user entry entirely. Silently skipped if the entry does not exist.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
choices: ['merge', 'replace', 'keep', 'remove']
|
||||
default: merge
|
||||
user:
|
||||
description:
|
||||
- User authentication configuration.
|
||||
- Not required when C(behavior) is V(remove).
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
token:
|
||||
description:
|
||||
@@ -188,7 +191,7 @@ options:
|
||||
- List of context definitions linking users and clusters.
|
||||
- Each context is identified by its C(name).
|
||||
- When C(name) matches an existing context, the default C(behavior) is V(merge).
|
||||
- See the C(behavior) suboption for V(replace) and V(keep).
|
||||
- See the C(behavior) suboption for V(replace), V(keep), and V(remove).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
@@ -205,14 +208,15 @@ options:
|
||||
- C(merge) - Update only the specified fields, preserve others (default).
|
||||
- C(replace) - Replace the entire context definition.
|
||||
- C(keep) - Keep existing context, skip this entry.
|
||||
- C(remove) - Remove the context entry entirely. Silently skipped if the entry does not exist.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
choices: ['merge', 'replace', 'keep', 'remove']
|
||||
default: merge
|
||||
context:
|
||||
description:
|
||||
- Context configuration linking cluster and user.
|
||||
- Not required when C(behavior) is V(remove).
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
cluster:
|
||||
description:
|
||||
@@ -277,27 +281,72 @@ EXAMPLES = r"""
|
||||
namespace: production
|
||||
current_context: prod-admin
|
||||
|
||||
- name: Copy and modify kubeconfig
|
||||
- name: Add a second cluster to an existing kubeconfig without touching other entries
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
dest: /home/user/.kube/config-backup
|
||||
clusters:
|
||||
- name: new-cluster
|
||||
- name: staging-cluster
|
||||
cluster:
|
||||
server: https://new.example.com:6443
|
||||
server: https://staging.k8s.example.com:6443
|
||||
insecure-skip-tls-verify: true
|
||||
users:
|
||||
- name: staging-user
|
||||
user:
|
||||
client-certificate: /path/to/staging.crt
|
||||
client-key: /path/to/staging.key
|
||||
contexts:
|
||||
- name: staging-admin
|
||||
context:
|
||||
cluster: staging-cluster
|
||||
user: staging-user
|
||||
namespace: staging
|
||||
|
||||
- name: Switch current context
|
||||
- name: Update only the token for an existing user, preserving all other user fields
|
||||
kubernetes.core.kubeconfig:
|
||||
path: ~/.kube/config
|
||||
current_context: prod-context
|
||||
|
||||
- name: Update user credentials
|
||||
kubernetes.core.kubeconfig:
|
||||
path: ~/.kube/config
|
||||
path: /home/user/.kube/config
|
||||
users:
|
||||
- name: admin-user
|
||||
behavior: merge
|
||||
user:
|
||||
token: "{{ new_admin_token }}"
|
||||
|
||||
- name: Replace a cluster definition entirely.
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
clusters:
|
||||
- name: production-cluster
|
||||
behavior: replace
|
||||
cluster:
|
||||
server: https://new-prod.k8s.example.com:6443
|
||||
certificate-authority-data: LS0tLS1CRUdJTi...
|
||||
|
||||
- name: Remove a decommissioned cluster, user, and context
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
clusters:
|
||||
- name: old-cluster
|
||||
behavior: remove
|
||||
users:
|
||||
- name: old-user
|
||||
behavior: remove
|
||||
contexts:
|
||||
- name: old-context
|
||||
behavior: remove
|
||||
|
||||
- name: Switch the active context
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
current_context: staging-admin
|
||||
|
||||
- name: Copy a kubeconfig to a new location with an additional cluster merged in
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
dest: /home/user/.kube/config-ci
|
||||
clusters:
|
||||
- name: ci-cluster
|
||||
cluster:
|
||||
server: https://ci.k8s.example.com:6443
|
||||
insecure-skip-tls-verify: true
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
assert:
|
||||
that:
|
||||
- update_result is changed
|
||||
- update_result.kubeconfig.clusters[0].cluster.server == "https://updated.example.com:6443"
|
||||
- update_result.kubeconfig.clusters | selectattr('name', 'equalto', test_cluster_name) | map(attribute='cluster') | map(attribute='server') | first == "https://updated.example.com:6443"
|
||||
|
||||
# Test 5: Check mode
|
||||
- name: Test check mode
|
||||
@@ -115,8 +115,72 @@
|
||||
check_mode: true
|
||||
register: check_mode_result
|
||||
|
||||
- name: Verify check mode didn't write
|
||||
- name: Verify check mode reports change but does not write
|
||||
assert:
|
||||
that:
|
||||
- check_mode_result is changed
|
||||
- check_mode_result.kubeconfig.clusters | length == 3 # Includes new cluster in output
|
||||
- check_mode_result.kubeconfig.clusters | length == 3
|
||||
|
||||
- name: Verify check mode cluster was not actually written to disk
|
||||
kubernetes.core.kubeconfig:
|
||||
path: "{{ test_config_path }}"
|
||||
register: after_check_mode
|
||||
|
||||
- name: Confirm check-mode-cluster is absent from disk
|
||||
assert:
|
||||
that:
|
||||
- after_check_mode.kubeconfig.clusters | selectattr('name', 'equalto', 'check-mode-cluster') | list | length == 0
|
||||
|
||||
# Test 6: Remove behavior
|
||||
- name: Remove cluster-2, user-2, and context-2
|
||||
kubernetes.core.kubeconfig:
|
||||
path: "{{ test_config_path }}"
|
||||
clusters:
|
||||
- name: cluster-2
|
||||
behavior: remove
|
||||
users:
|
||||
- name: user-2
|
||||
behavior: remove
|
||||
contexts:
|
||||
- name: context-2
|
||||
behavior: remove
|
||||
register: remove_result
|
||||
|
||||
- name: Verify entries were removed
|
||||
assert:
|
||||
that:
|
||||
- remove_result is changed
|
||||
- remove_result.kubeconfig.clusters | selectattr('name', 'equalto', 'cluster-2') | list | length == 0
|
||||
- remove_result.kubeconfig.users | selectattr('name', 'equalto', 'user-2') | list | length == 0
|
||||
- remove_result.kubeconfig.contexts | selectattr('name', 'equalto', 'context-2') | list | length == 0
|
||||
|
||||
# Test 7: Remove behavior is idempotent when entry does not exist
|
||||
- name: Remove already-absent entry
|
||||
kubernetes.core.kubeconfig:
|
||||
path: "{{ test_config_path }}"
|
||||
clusters:
|
||||
- name: cluster-2
|
||||
behavior: remove
|
||||
register: remove_idempotent_result
|
||||
|
||||
- name: Verify no change when removing nonexistent entry
|
||||
assert:
|
||||
that:
|
||||
- remove_idempotent_result is not changed
|
||||
|
||||
# Test 8: Keep behavior protects existing entry
|
||||
- name: Attempt to overwrite protected cluster
|
||||
kubernetes.core.kubeconfig:
|
||||
path: "{{ test_config_path }}"
|
||||
clusters:
|
||||
- name: "{{ test_cluster_name }}"
|
||||
behavior: keep
|
||||
cluster:
|
||||
server: https://should-not-apply.example.com:6443
|
||||
register: keep_result
|
||||
|
||||
- name: Verify keep behavior left existing entry unchanged
|
||||
assert:
|
||||
that:
|
||||
- keep_result is not changed
|
||||
- keep_result.kubeconfig.clusters | selectattr('name', 'equalto', test_cluster_name) | map(attribute='cluster') | map(attribute='server') | first == "https://updated.example.com:6443"
|
||||
|
||||
@@ -141,6 +141,39 @@ def test_merge_by_name_keep_behavior_preserves_existing():
|
||||
assert result[0]["cluster"]["server"] == "https://old.com"
|
||||
|
||||
|
||||
def test_merge_by_name_remove_behavior_removes_existing_entry():
|
||||
existing = [{"name": "cluster-a", "cluster": {"server": "https://a.com"}}]
|
||||
new = [{"name": "cluster-a", "behavior": "remove"}]
|
||||
result = merge_by_name(existing, new)
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_merge_by_name_remove_behavior_only_removes_target_entry():
|
||||
existing = [
|
||||
{"name": "cluster-a", "cluster": {"server": "https://a.com"}},
|
||||
{"name": "cluster-b", "cluster": {"server": "https://b.com"}},
|
||||
]
|
||||
new = [{"name": "cluster-a", "behavior": "remove"}]
|
||||
result = merge_by_name(existing, new)
|
||||
assert len(result) == 1
|
||||
assert result[0]["name"] == "cluster-b"
|
||||
|
||||
|
||||
def test_merge_by_name_remove_behavior_silently_skips_nonexistent_entry():
|
||||
existing = [{"name": "cluster-a", "cluster": {"server": "https://a.com"}}]
|
||||
new = [{"name": "cluster-nonexistent", "behavior": "remove"}]
|
||||
result = merge_by_name(existing, new)
|
||||
assert len(result) == 1
|
||||
assert result[0]["name"] == "cluster-a"
|
||||
|
||||
|
||||
def test_merge_by_name_remove_behavior_on_empty_existing():
|
||||
existing = []
|
||||
new = [{"name": "cluster-a", "behavior": "remove"}]
|
||||
result = merge_by_name(existing, new)
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_merge_by_name_behavior_key_not_in_output():
|
||||
existing = []
|
||||
new = [
|
||||
|
||||
Reference in New Issue
Block a user