mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-07 05:22:39 +00:00
Add kubeconfig module for managing Kubernetes config files (#1104)
* 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 --------- Co-authored-by: Bianca Henderson <bianca@redhat.com>
This commit is contained in:
441
plugins/modules/kubeconfig.py
Normal file
441
plugins/modules/kubeconfig.py
Normal file
@@ -0,0 +1,441 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
---
|
||||
module: kubeconfig
|
||||
|
||||
short_description: Generate, update, and optionally write Kubernetes kubeconfig files
|
||||
|
||||
version_added: "6.5.0"
|
||||
|
||||
author: "Youssef Khalid Ali (@YoussefKhalidAli)"
|
||||
|
||||
description:
|
||||
- Build, update, and manage Kubernetes kubeconfig files using structured input.
|
||||
- Supports loading an existing kubeconfig file and merging clusters, users, and contexts.
|
||||
- Can optionally write the resulting kubeconfig to a destination path.
|
||||
- Ensures idempotent behavior by only updating files when changes occur.
|
||||
|
||||
requirements:
|
||||
- "PyYAML >= 5.1"
|
||||
|
||||
notes:
|
||||
- Input data is merged by resource name (cluster, user, context).
|
||||
- Updates under O(clusters), O(users), and O(contexts) are matched by C(name) against the kubeconfig loaded from O(path).
|
||||
- For an existing C(name), each entry's C(behavior) suboption controls the update.
|
||||
- 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.
|
||||
- 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.
|
||||
- The structure follows standard Kubernetes kubeconfig format as defined in the Kubernetes documentation.
|
||||
- Tokens and sensitive data should be protected using ansible-vault or environment variables.
|
||||
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path to an existing kubeconfig file to load and merge from.
|
||||
- If the file does not exist, a new kubeconfig will be created.
|
||||
- This becomes the default destination if O(dest) is not specified.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
dest:
|
||||
description:
|
||||
- Destination path where the final kubeconfig should be written.
|
||||
- If not specified, the kubeconfig will be saved to O(path).
|
||||
- Allows copying and modifying a kubeconfig to a new location.
|
||||
type: str
|
||||
required: false
|
||||
|
||||
clusters:
|
||||
description:
|
||||
- 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).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
default: []
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Unique name identifier for the cluster.
|
||||
type: str
|
||||
required: true
|
||||
behavior:
|
||||
description:
|
||||
- How to handle merging if a cluster with this name already exists.
|
||||
- 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.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
default: merge
|
||||
cluster:
|
||||
description:
|
||||
- Cluster configuration details.
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
server:
|
||||
description:
|
||||
- Kubernetes API server URL (e.g., C(https://k8s.example.com:6443)).
|
||||
type: str
|
||||
required: true
|
||||
certificate-authority:
|
||||
description:
|
||||
- Path to a CA certificate file for validating the API server certificate.
|
||||
type: str
|
||||
certificate-authority-data:
|
||||
description:
|
||||
- Base64 encoded CA certificate data.
|
||||
- Use this instead of C(certificate-authority) for embedded certificates.
|
||||
type: str
|
||||
insecure-skip-tls-verify:
|
||||
description:
|
||||
- If true, the server's certificate will not be validated.
|
||||
type: bool
|
||||
proxy-url:
|
||||
description:
|
||||
- Optional proxy URL for cluster connections.
|
||||
type: str
|
||||
tls-server-name:
|
||||
description:
|
||||
- Server name to use for server certificate validation.
|
||||
type: str
|
||||
|
||||
users:
|
||||
description:
|
||||
- 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).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
default: []
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Unique name identifier for the user.
|
||||
type: str
|
||||
required: true
|
||||
behavior:
|
||||
description:
|
||||
- How to handle merging if a user with this name already exists.
|
||||
- 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.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
default: merge
|
||||
user:
|
||||
description:
|
||||
- User authentication configuration.
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
token:
|
||||
description:
|
||||
- Bearer token for authentication.
|
||||
type: str
|
||||
username:
|
||||
description:
|
||||
- Username for basic authentication.
|
||||
type: str
|
||||
password:
|
||||
description:
|
||||
- Password for basic authentication.
|
||||
type: str
|
||||
client-certificate:
|
||||
description:
|
||||
- Path to client certificate file.
|
||||
- Used for certificate-based authentication.
|
||||
type: str
|
||||
client-key:
|
||||
description:
|
||||
- Path to client private key file.
|
||||
- Must be provided with C(client-certificate).
|
||||
type: str
|
||||
client-certificate-data:
|
||||
description:
|
||||
- Base64 encoded client certificate.
|
||||
- Use instead of C(client-certificate) for embedded certificates.
|
||||
type: str
|
||||
client-key-data:
|
||||
description:
|
||||
- Base64 encoded client private key.
|
||||
- Use instead of C(client-key) for embedded keys.
|
||||
type: str
|
||||
auth-provider:
|
||||
description:
|
||||
- Authentication provider configuration (e.g., for GCP, Azure).
|
||||
type: dict
|
||||
exec:
|
||||
description:
|
||||
- Exec-based credential plugin configuration.
|
||||
- Used for external authentication providers.
|
||||
type: dict
|
||||
|
||||
contexts:
|
||||
description:
|
||||
- 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).
|
||||
type: list
|
||||
elements: dict
|
||||
required: false
|
||||
default: []
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Unique name identifier for the context.
|
||||
type: str
|
||||
required: true
|
||||
behavior:
|
||||
description:
|
||||
- How to handle merging if a context with this name already exists.
|
||||
- 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.
|
||||
type: str
|
||||
choices: ['merge', 'replace', 'keep']
|
||||
default: merge
|
||||
context:
|
||||
description:
|
||||
- Context configuration linking cluster and user.
|
||||
type: dict
|
||||
required: true
|
||||
suboptions:
|
||||
cluster:
|
||||
description:
|
||||
- Name of the cluster to use (must match a cluster name in O(clusters)).
|
||||
type: str
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- Name of the user to authenticate as (must match a user name in O(users)).
|
||||
type: str
|
||||
required: true
|
||||
namespace:
|
||||
description:
|
||||
- Default namespace to use for this context.
|
||||
- If not specified, defaults to C(default).
|
||||
type: str
|
||||
|
||||
preferences:
|
||||
description:
|
||||
- Kubeconfig preferences.
|
||||
- Used for client-side settings like color output, default editor, etc.
|
||||
type: dict
|
||||
required: false
|
||||
default: {}
|
||||
|
||||
current_context:
|
||||
description:
|
||||
- Name of the context to set as current/active.
|
||||
- This context will be used by default when using kubectl.
|
||||
- Must match one of the context names defined in O(contexts).
|
||||
type: str
|
||||
required: false
|
||||
|
||||
seealso:
|
||||
- name: Kubernetes kubeconfig documentation
|
||||
description: Official Kubernetes documentation for kubeconfig files
|
||||
link: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
|
||||
- name: kubectl config documentation
|
||||
description: kubectl commands for working with kubeconfig files
|
||||
link: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_config/
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# Create a new kubeconfig file with a single cluster
|
||||
- name: Create basic kubeconfig
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
clusters:
|
||||
- name: production-cluster
|
||||
cluster:
|
||||
server: https://prod.k8s.example.com:6443
|
||||
certificate-authority-data: LS0tLS1CRUdJTi...
|
||||
users:
|
||||
- name: admin-user
|
||||
user:
|
||||
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9...
|
||||
contexts:
|
||||
- name: prod-admin
|
||||
context:
|
||||
cluster: production-cluster
|
||||
user: admin-user
|
||||
namespace: production
|
||||
current_context: prod-admin
|
||||
|
||||
- name: Copy and modify kubeconfig
|
||||
kubernetes.core.kubeconfig:
|
||||
path: /home/user/.kube/config
|
||||
dest: /home/user/.kube/config-backup
|
||||
clusters:
|
||||
- name: new-cluster
|
||||
cluster:
|
||||
server: https://new.example.com:6443
|
||||
|
||||
- name: Switch current context
|
||||
kubernetes.core.kubeconfig:
|
||||
path: ~/.kube/config
|
||||
current_context: prod-context
|
||||
|
||||
- name: Update user credentials
|
||||
kubernetes.core.kubeconfig:
|
||||
path: ~/.kube/config
|
||||
users:
|
||||
- name: admin-user
|
||||
user:
|
||||
token: "{{ new_admin_token }}"
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
kubeconfig:
|
||||
description: The complete kubeconfig data structure.
|
||||
type: dict
|
||||
returned: always
|
||||
|
||||
dest:
|
||||
description: The path where the kubeconfig was written.
|
||||
type: str
|
||||
returned: always
|
||||
sample: /home/user/.kube/config
|
||||
"""
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
extract_sensitive_values_from_kubeconfig,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.kubeconfig import (
|
||||
hash_data,
|
||||
load_yaml_file,
|
||||
merge_by_name,
|
||||
write_file,
|
||||
)
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
||||
IMP_YAML = True
|
||||
IMP_YAML_ERR = None
|
||||
except ImportError:
|
||||
IMP_YAML = False
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
|
||||
|
||||
def run_module():
|
||||
module_args = dict(
|
||||
path=dict(type="str", required=True),
|
||||
dest=dict(type="str", required=False),
|
||||
clusters=dict(type="list", elements="dict", required=False, default=[]),
|
||||
users=dict(type="list", elements="dict", required=False, default=[]),
|
||||
contexts=dict(type="list", elements="dict", required=False, default=[]),
|
||||
preferences=dict(type="dict", required=False, default={}),
|
||||
current_context=dict(type="str", required=False),
|
||||
)
|
||||
|
||||
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
|
||||
|
||||
path = module.params["path"]
|
||||
dest = module.params["dest"] or path
|
||||
|
||||
clusters_input = module.params["clusters"]
|
||||
users_input = module.params["users"]
|
||||
contexts_input = module.params["contexts"]
|
||||
|
||||
preferences = module.params["preferences"]
|
||||
current_context = module.params["current_context"]
|
||||
|
||||
# Load existing kubeconfig
|
||||
try:
|
||||
if not IMP_YAML:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("pyyaml"),
|
||||
exception=IMP_YAML_ERR,
|
||||
)
|
||||
existing = load_yaml_file(path) if path else {}
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg="Failed to load existing kubeconfig: %s" % to_native(e),
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
|
||||
clusters = merge_by_name(existing.get("clusters", []), clusters_input)
|
||||
users = merge_by_name(existing.get("users", []), users_input)
|
||||
contexts = merge_by_name(existing.get("contexts", []), contexts_input)
|
||||
|
||||
# Build final kubeconfig
|
||||
kubeconfig = {
|
||||
"apiVersion": "v1",
|
||||
"kind": "Config",
|
||||
"preferences": preferences or existing.get("preferences", {}),
|
||||
"clusters": clusters,
|
||||
"users": users,
|
||||
"contexts": contexts,
|
||||
"current-context": current_context or existing.get("current-context") or "",
|
||||
}
|
||||
|
||||
changed = False
|
||||
old_data = {}
|
||||
|
||||
if os.path.exists(dest):
|
||||
try:
|
||||
with open(dest, "r") as f:
|
||||
old_data = yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg="Failed to read destination file: %s" % to_native(e),
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
|
||||
old_hash = hash_data(old_data)
|
||||
new_hash = hash_data(kubeconfig)
|
||||
|
||||
if old_hash != new_hash:
|
||||
if not module.check_mode:
|
||||
try:
|
||||
write_file(dest, kubeconfig)
|
||||
except Exception as e:
|
||||
module.fail_json(
|
||||
msg="Failed to write kubeconfig: %s" % to_native(e),
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
changed = True
|
||||
|
||||
if isinstance(kubeconfig, dict):
|
||||
module.no_log_values.update(
|
||||
extract_sensitive_values_from_kubeconfig(kubeconfig)
|
||||
)
|
||||
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
kubeconfig=kubeconfig,
|
||||
dest=dest,
|
||||
msg=(
|
||||
"Kubeconfig file has been updated."
|
||||
if changed
|
||||
else "Kubeconfig file is already up to date."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user