mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-05-06 04:52:37 +00:00
Merge remote-tracking branch 'upstream/main' into merge-upstream
This commit is contained in:
1
plugins/action/helm.py
Symbolic link
1
plugins/action/helm.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_info.py
Symbolic link
1
plugins/action/helm_info.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_plugin.py
Symbolic link
1
plugins/action/helm_plugin.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_plugin_info.py
Symbolic link
1
plugins/action/helm_plugin_info.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/helm_repository.py
Symbolic link
1
plugins/action/helm_repository.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s.py
Symbolic link
1
plugins/action/k8s.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_auth.py
Symbolic link
1
plugins/action/k8s_auth.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_exec.py
Symbolic link
1
plugins/action/k8s_exec.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
82
plugins/action/k8s_info.py
Normal file
82
plugins/action/k8s_info.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Copyright (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright (c) 2017, Toshio Kuratomi <tkuraotmi@ansible.com>
|
||||
# Copyright (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleError
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
TRANSFERS_FILES = True
|
||||
|
||||
def _ensure_invocation(self, result):
|
||||
# NOTE: adding invocation arguments here needs to be kept in sync with
|
||||
# any no_log specified in the argument_spec in the module.
|
||||
if 'invocation' not in result:
|
||||
if self._play_context.no_log:
|
||||
result['invocation'] = "CENSORED: no_log is set"
|
||||
else:
|
||||
result['invocation'] = self._task.args.copy()
|
||||
result['invocation']['module_args'] = self._task.args.copy()
|
||||
|
||||
return result
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
''' handler for k8s options '''
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
new_module_args = copy.deepcopy(self._task.args)
|
||||
kubeconfig = self._task.args.get('kubeconfig', None)
|
||||
# find the file in the expected search path
|
||||
if kubeconfig:
|
||||
try:
|
||||
# find in expected paths
|
||||
kubeconfig = self._find_needle('files', kubeconfig)
|
||||
except AnsibleError as e:
|
||||
result['failed'] = True
|
||||
result['msg'] = to_text(e)
|
||||
result['exception'] = traceback.format_exc()
|
||||
return result
|
||||
|
||||
if kubeconfig:
|
||||
# decrypt kubeconfig found
|
||||
actual_file = self._loader.get_real_file(kubeconfig, decrypt=True)
|
||||
new_module_args['kubeconfig'] = actual_file
|
||||
|
||||
# find the file in the expected search path
|
||||
src = self._task.args.get('src', None)
|
||||
if src:
|
||||
try:
|
||||
# find in expected paths
|
||||
src = self._find_needle('files', src)
|
||||
except AnsibleError as e:
|
||||
result['failed'] = True
|
||||
result['msg'] = to_text(e)
|
||||
result['exception'] = traceback.format_exc()
|
||||
return result
|
||||
|
||||
if src:
|
||||
new_module_args['src'] = src
|
||||
|
||||
# Execute the k8s_* module.
|
||||
module_return = self._execute_module(module_name=self._task.action, module_args=new_module_args, task_vars=task_vars)
|
||||
|
||||
# Delete tmp path
|
||||
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||
|
||||
result.update(module_return)
|
||||
|
||||
return self._ensure_invocation(result)
|
||||
1
plugins/action/k8s_log.py
Symbolic link
1
plugins/action/k8s_log.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_scale.py
Symbolic link
1
plugins/action/k8s_scale.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
1
plugins/action/k8s_service.py
Symbolic link
1
plugins/action/k8s_service.py
Symbolic link
@@ -0,0 +1 @@
|
||||
k8s_info.py
|
||||
@@ -20,7 +20,7 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
DOCUMENTATION = r"""
|
||||
author:
|
||||
- xuxinkun
|
||||
|
||||
@@ -38,7 +38,8 @@ DOCUMENTATION = """
|
||||
options:
|
||||
kubectl_pod:
|
||||
description:
|
||||
- Pod name. Required when the host name does not match pod name.
|
||||
- Pod name.
|
||||
- Required when the host name does not match pod name.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_kubectl_pod
|
||||
@@ -46,7 +47,8 @@ DOCUMENTATION = """
|
||||
- name: K8S_AUTH_POD
|
||||
kubectl_container:
|
||||
description:
|
||||
- Container name. Required when a pod contains more than one container.
|
||||
- Container name.
|
||||
- Required when a pod contains more than one container.
|
||||
default: ''
|
||||
vars:
|
||||
- name: ansible_kubectl_container
|
||||
@@ -173,7 +175,6 @@ import os
|
||||
import os.path
|
||||
import subprocess
|
||||
|
||||
import ansible.constants as C
|
||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||
from ansible.errors import AnsibleError, AnsibleFileNotFound
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
@@ -228,6 +229,7 @@ class Connection(ConnectionBase):
|
||||
""" Build the local kubectl exec command to run cmd on remote_host
|
||||
"""
|
||||
local_cmd = [self.transport_cmd]
|
||||
censored_local_cmd = [self.transport_cmd]
|
||||
|
||||
# Build command options based on doc string
|
||||
doc_yaml = AnsibleLoader(self.documentation).get_single_data()
|
||||
@@ -236,28 +238,36 @@ class Connection(ConnectionBase):
|
||||
# Translate verify_ssl to skip_verify_ssl, and output as string
|
||||
skip_verify_ssl = not self.get_option(key)
|
||||
local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
||||
censored_local_cmd.append(u'{0}={1}'.format(self.connection_options[key], str(skip_verify_ssl).lower()))
|
||||
elif not key.endswith('container') and self.get_option(key) and self.connection_options.get(key):
|
||||
cmd_arg = self.connection_options[key]
|
||||
local_cmd += [cmd_arg, self.get_option(key)]
|
||||
# Redact password and token from console log
|
||||
if key.endswith(('_token', '_password')):
|
||||
censored_local_cmd += [cmd_arg, '********']
|
||||
|
||||
extra_args_name = u'{0}_extra_args'.format(self.transport)
|
||||
if self.get_option(extra_args_name):
|
||||
local_cmd += self.get_option(extra_args_name).split(' ')
|
||||
censored_local_cmd += self.get_option(extra_args_name).split(' ')
|
||||
|
||||
pod = self.get_option(u'{0}_pod'.format(self.transport))
|
||||
if not pod:
|
||||
pod = self._play_context.remote_addr
|
||||
# -i is needed to keep stdin open which allows pipelining to work
|
||||
local_cmd += ['exec', '-i', pod]
|
||||
censored_local_cmd += ['exec', '-i', pod]
|
||||
|
||||
# if the pod has more than one container, then container is required
|
||||
container_arg_name = u'{0}_container'.format(self.transport)
|
||||
if self.get_option(container_arg_name):
|
||||
local_cmd += ['-c', self.get_option(container_arg_name)]
|
||||
censored_local_cmd += ['-c', self.get_option(container_arg_name)]
|
||||
|
||||
local_cmd += ['--'] + cmd
|
||||
censored_local_cmd += ['--'] + cmd
|
||||
|
||||
return local_cmd
|
||||
return local_cmd, censored_local_cmd
|
||||
|
||||
def _connect(self, port=None):
|
||||
""" Connect to the container. Nothing to do """
|
||||
@@ -270,9 +280,9 @@ class Connection(ConnectionBase):
|
||||
""" Run a command in the container """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
|
||||
local_cmd, censored_local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd,), host=self._play_context.remote_addr)
|
||||
display.vvv("EXEC %s" % (censored_local_cmd,), host=self._play_context.remote_addr)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -312,7 +322,7 @@ class Connection(ConnectionBase):
|
||||
count = ' count=0'
|
||||
else:
|
||||
count = ''
|
||||
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
|
||||
args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
|
||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||
try:
|
||||
p = subprocess.Popen(args, stdin=in_file,
|
||||
@@ -334,7 +344,7 @@ class Connection(ConnectionBase):
|
||||
|
||||
# kubectl doesn't have native support for fetching files from
|
||||
# running containers, so we use kubectl exec to implement this
|
||||
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
|
||||
args, dummy = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
|
||||
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
|
||||
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
|
||||
with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
|
||||
|
||||
34
plugins/doc_fragments/helm_common_options.py
Normal file
34
plugins/doc_fragments/helm_common_options.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# Copyright: (c) 2020, Red Hat Inc.
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Options for common Helm modules
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
binary_path:
|
||||
description:
|
||||
- The path of a helm binary to use.
|
||||
required: false
|
||||
type: path
|
||||
context:
|
||||
description:
|
||||
- Helm option to specify which kubeconfig context to use.
|
||||
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_CONTEXT) will be used instead.
|
||||
type: str
|
||||
aliases: [ kube_context ]
|
||||
kubeconfig:
|
||||
description:
|
||||
- Helm option to specify kubeconfig path to use.
|
||||
- If the value is not specified in the task, the value of environment variable C(K8S_AUTH_KUBECONFIG) will be used instead.
|
||||
type: path
|
||||
aliases: [ kubeconfig_path ]
|
||||
'''
|
||||
@@ -15,9 +15,10 @@ class ModuleDocFragment(object):
|
||||
options:
|
||||
api_version:
|
||||
description:
|
||||
- Use to specify the API version. Use to create, delete, or discover an object without providing a full
|
||||
resource definition. Use in conjunction with I(kind), I(name), and I(namespace) to identify a
|
||||
specific object. If I(resource definition) is provided, the I(apiVersion) from the I(resource_definition)
|
||||
- Use to specify the API version.
|
||||
- Use to create, delete, or discover an object without providing a full resource definition.
|
||||
- Use in conjunction with I(kind), I(name), and I(namespace) to identify a specific object.
|
||||
- If I(resource definition) is provided, the I(apiVersion) value from the I(resource_definition)
|
||||
will override this option.
|
||||
type: str
|
||||
default: v1
|
||||
@@ -26,23 +27,26 @@ options:
|
||||
- version
|
||||
kind:
|
||||
description:
|
||||
- Use to specify an object model. Use to create, delete, or discover an object without providing a full
|
||||
resource definition. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a
|
||||
specific object. If I(resource definition) is provided, the I(kind) from the I(resource_definition)
|
||||
- Use to specify an object model.
|
||||
- Use to create, delete, or discover an object without providing a full resource definition.
|
||||
- Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
|
||||
- If I(resource definition) is provided, the I(kind) value from the I(resource_definition)
|
||||
will override this option.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Use to specify an object name. Use to create, delete, or discover an object without providing a full
|
||||
resource definition. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a
|
||||
specific object. If I(resource definition) is provided, the I(metadata.name) value from the
|
||||
I(resource_definition) will override this option.
|
||||
- Use to specify an object name.
|
||||
- Use to create, delete, or discover an object without providing a full resource definition.
|
||||
- Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a specific object.
|
||||
- If I(resource definition) is provided, the I(metadata.name) value from the I(resource_definition)
|
||||
will override this option.
|
||||
type: str
|
||||
namespace:
|
||||
description:
|
||||
- Use to specify an object namespace. Useful when creating, deleting, or discovering an object without
|
||||
providing a full resource definition. Use in conjunction with I(api_version), I(kind), and I(name)
|
||||
to identify a specfic object. If I(resource definition) is provided, the I(metadata.namespace) value
|
||||
from the I(resource_definition) will override this option.
|
||||
- Use to specify an object namespace.
|
||||
- Useful when creating, deleting, or discovering an object without providing a full resource definition.
|
||||
- Use in conjunction with I(api_version), I(kind), and I(name) to identify a specific object.
|
||||
- If I(resource definition) is provided, the I(metadata.namespace) value from the I(resource_definition)
|
||||
will override this option.
|
||||
type: str
|
||||
'''
|
||||
|
||||
@@ -15,8 +15,8 @@ class ModuleDocFragment(object):
|
||||
options:
|
||||
resource_definition:
|
||||
description:
|
||||
- "Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating. NOTE: I(kind), I(api_version), I(name),
|
||||
and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)."
|
||||
- Provide a valid YAML definition (either as a string, list, or dict) for an object when creating or updating.
|
||||
- "NOTE: I(kind), I(api_version), I(name), and I(namespace) will be overwritten by corresponding values found in the provided I(resource_definition)."
|
||||
aliases:
|
||||
- definition
|
||||
- inline
|
||||
|
||||
@@ -6,13 +6,6 @@ from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
|
||||
try:
|
||||
from openshift.helper.hashes import generate_hash
|
||||
HAS_GENERATE_HASH = True
|
||||
|
||||
@@ -28,60 +28,61 @@ DOCUMENTATION = '''
|
||||
- Optional list of cluster connection settings. If no connections are provided, the default
|
||||
I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces
|
||||
the active user is authorized to access.
|
||||
name:
|
||||
description:
|
||||
- Optional name to assign to the cluster. If not provided, a name is constructed from the server
|
||||
and port.
|
||||
kubeconfig:
|
||||
description:
|
||||
- Path to an existing Kubernetes config file. If not provided, and no other connection
|
||||
options are provided, the OpenShift client will attempt to load the default
|
||||
configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG
|
||||
environment variable.
|
||||
context:
|
||||
description:
|
||||
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
|
||||
variable.
|
||||
host:
|
||||
description:
|
||||
- Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
|
||||
api_key:
|
||||
description:
|
||||
- Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment
|
||||
variable.
|
||||
username:
|
||||
description:
|
||||
- Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME
|
||||
environment variable.
|
||||
password:
|
||||
description:
|
||||
- Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD
|
||||
environment variable.
|
||||
client_cert:
|
||||
description:
|
||||
- Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
|
||||
environment variable.
|
||||
aliases: [ cert_file ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE
|
||||
environment variable.
|
||||
aliases: [ key_file ]
|
||||
ca_cert:
|
||||
description:
|
||||
- Path to a CA certificate used to authenticate with the API. Can also be specified via
|
||||
K8S_AUTH_SSL_CA_CERT environment variable.
|
||||
aliases: [ ssl_ca_cert ]
|
||||
validate_certs:
|
||||
description:
|
||||
- "Whether or not to verify the API server's SSL certificates. Can also be specified via
|
||||
K8S_AUTH_VERIFY_SSL environment variable."
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
namespaces:
|
||||
description:
|
||||
- List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized
|
||||
to access.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Optional name to assign to the cluster. If not provided, a name is constructed from the server
|
||||
and port.
|
||||
kubeconfig:
|
||||
description:
|
||||
- Path to an existing Kubernetes config file. If not provided, and no other connection
|
||||
options are provided, the OpenShift client will attempt to load the default
|
||||
configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG
|
||||
environment variable.
|
||||
context:
|
||||
description:
|
||||
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
|
||||
variable.
|
||||
host:
|
||||
description:
|
||||
- Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
|
||||
api_key:
|
||||
description:
|
||||
- Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment
|
||||
variable.
|
||||
username:
|
||||
description:
|
||||
- Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME
|
||||
environment variable.
|
||||
password:
|
||||
description:
|
||||
- Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD
|
||||
environment variable.
|
||||
client_cert:
|
||||
description:
|
||||
- Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
|
||||
environment variable.
|
||||
aliases: [ cert_file ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE
|
||||
environment variable.
|
||||
aliases: [ key_file ]
|
||||
ca_cert:
|
||||
description:
|
||||
- Path to a CA certificate used to authenticate with the API. Can also be specified via
|
||||
K8S_AUTH_SSL_CA_CERT environment variable.
|
||||
aliases: [ ssl_ca_cert ]
|
||||
validate_certs:
|
||||
description:
|
||||
- "Whether or not to verify the API server's SSL certificates. Can also be specified via
|
||||
K8S_AUTH_VERIFY_SSL environment variable."
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
namespaces:
|
||||
description:
|
||||
- List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized
|
||||
to access.
|
||||
|
||||
requirements:
|
||||
- "python >= 2.7"
|
||||
@@ -93,20 +94,20 @@ EXAMPLES = '''
|
||||
# File must be named k8s.yaml or k8s.yml
|
||||
|
||||
# Authenticate with token, and return all pods and services for all namespaces
|
||||
plugin: k8s
|
||||
plugin: community.kubernetes.k8s
|
||||
connections:
|
||||
- host: https://192.168.64.4:8443
|
||||
token: xxxxxxxxxxxxxxxx
|
||||
api_key: xxxxxxxxxxxxxxxx
|
||||
validate_certs: false
|
||||
|
||||
# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
|
||||
plugin: k8s
|
||||
plugin: community.kubernetes.k8s
|
||||
connections:
|
||||
- namespaces:
|
||||
- testing
|
||||
|
||||
# Use a custom config file, and a specific context.
|
||||
plugin: k8s
|
||||
plugin: community.kubernetes.k8s
|
||||
connections:
|
||||
- kubeconfig: /path/to/config
|
||||
context: 'awx/192-168-64-4:8443/developer'
|
||||
@@ -142,6 +143,7 @@ class K8sInventoryException(Exception):
|
||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleMixin):
|
||||
NAME = 'community.kubernetes.k8s'
|
||||
|
||||
connection_plugin = 'kubectl'
|
||||
transport = 'kubectl'
|
||||
|
||||
def parse(self, inventory, loader, path, cache=True):
|
||||
@@ -275,7 +277,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
||||
self.inventory.set_variable(container_name, 'container_state', 'Waiting')
|
||||
self.inventory.set_variable(container_name, 'container_ready', container.ready)
|
||||
self.inventory.set_variable(container_name, 'ansible_remote_tmp', '/tmp/')
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', self.transport)
|
||||
self.inventory.set_variable(container_name, 'ansible_connection', self.connection_plugin)
|
||||
self.inventory.set_variable(container_name, 'ansible_{0}_pod'.format(self.transport),
|
||||
pod_name)
|
||||
self.inventory.set_variable(container_name, 'ansible_{0}_container'.format(self.transport),
|
||||
@@ -316,7 +318,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable, K8sAnsibleM
|
||||
|
||||
try:
|
||||
self.inventory.add_child(namespace_services_group, service_name)
|
||||
except AnsibleError as e:
|
||||
except AnsibleError:
|
||||
raise
|
||||
|
||||
ports = [{'name': port.name,
|
||||
|
||||
@@ -28,60 +28,61 @@ DOCUMENTATION = '''
|
||||
- Optional list of cluster connection settings. If no connections are provided, the default
|
||||
I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces
|
||||
the active user is authorized to access.
|
||||
name:
|
||||
description:
|
||||
- Optional name to assign to the cluster. If not provided, a name is constructed from the server
|
||||
and port.
|
||||
kubeconfig:
|
||||
description:
|
||||
- Path to an existing Kubernetes config file. If not provided, and no other connection
|
||||
options are provided, the OpenShift client will attempt to load the default
|
||||
configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG
|
||||
environment variable.
|
||||
context:
|
||||
description:
|
||||
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
|
||||
variable.
|
||||
host:
|
||||
description:
|
||||
- Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
|
||||
api_key:
|
||||
description:
|
||||
- Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment
|
||||
variable.
|
||||
username:
|
||||
description:
|
||||
- Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME
|
||||
environment variable.
|
||||
password:
|
||||
description:
|
||||
- Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD
|
||||
environment variable.
|
||||
client_cert:
|
||||
description:
|
||||
- Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
|
||||
environment variable.
|
||||
aliases: [ cert_file ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE
|
||||
environment variable.
|
||||
aliases: [ key_file ]
|
||||
ca_cert:
|
||||
description:
|
||||
- Path to a CA certificate used to authenticate with the API. Can also be specified via
|
||||
K8S_AUTH_SSL_CA_CERT environment variable.
|
||||
aliases: [ ssl_ca_cert ]
|
||||
validate_certs:
|
||||
description:
|
||||
- "Whether or not to verify the API server's SSL certificates. Can also be specified via
|
||||
K8S_AUTH_VERIFY_SSL environment variable."
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
namespaces:
|
||||
description:
|
||||
- List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized
|
||||
to access.
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- Optional name to assign to the cluster. If not provided, a name is constructed from the server
|
||||
and port.
|
||||
kubeconfig:
|
||||
description:
|
||||
- Path to an existing Kubernetes config file. If not provided, and no other connection
|
||||
options are provided, the OpenShift client will attempt to load the default
|
||||
configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG
|
||||
environment variable.
|
||||
context:
|
||||
description:
|
||||
- The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment
|
||||
variable.
|
||||
host:
|
||||
description:
|
||||
- Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable.
|
||||
api_key:
|
||||
description:
|
||||
- Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment
|
||||
variable.
|
||||
username:
|
||||
description:
|
||||
- Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME
|
||||
environment variable.
|
||||
password:
|
||||
description:
|
||||
- Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD
|
||||
environment variable.
|
||||
client_cert:
|
||||
description:
|
||||
- Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE
|
||||
environment variable.
|
||||
aliases: [ cert_file ]
|
||||
client_key:
|
||||
description:
|
||||
- Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE
|
||||
environment variable.
|
||||
aliases: [ key_file ]
|
||||
ca_cert:
|
||||
description:
|
||||
- Path to a CA certificate used to authenticate with the API. Can also be specified via
|
||||
K8S_AUTH_SSL_CA_CERT environment variable.
|
||||
aliases: [ ssl_ca_cert ]
|
||||
validate_certs:
|
||||
description:
|
||||
- "Whether or not to verify the API server's SSL certificates. Can also be specified via
|
||||
K8S_AUTH_VERIFY_SSL environment variable."
|
||||
type: bool
|
||||
aliases: [ verify_ssl ]
|
||||
namespaces:
|
||||
description:
|
||||
- List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized
|
||||
to access.
|
||||
|
||||
requirements:
|
||||
- "python >= 2.7"
|
||||
@@ -93,20 +94,20 @@ EXAMPLES = '''
|
||||
# File must be named openshift.yaml or openshift.yml
|
||||
|
||||
# Authenticate with token, and return all pods and services for all namespaces
|
||||
plugin: openshift
|
||||
plugin: community.kubernetes.openshift
|
||||
connections:
|
||||
- host: https://192.168.64.4:8443
|
||||
api_key: xxxxxxxxxxxxxxxx
|
||||
verify_ssl: false
|
||||
|
||||
# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace
|
||||
plugin: openshift
|
||||
plugin: community.kubernetes.openshift
|
||||
connections:
|
||||
- namespaces:
|
||||
- testing
|
||||
|
||||
# Use a custom config file, and a specific context.
|
||||
plugin: openshift
|
||||
plugin: community.kubernetes.openshift
|
||||
connections:
|
||||
- kubeconfig: /path/to/config
|
||||
context: 'awx/192-168-64-4:8443/developer'
|
||||
|
||||
@@ -133,23 +133,23 @@ DOCUMENTATION = '''
|
||||
EXAMPLES = """
|
||||
- name: Fetch a list of namespaces
|
||||
set_fact:
|
||||
projects: "{{ lookup('k8s', api_version='v1', kind='Namespace') }}"
|
||||
projects: "{{ lookup('community.kubernetes.k8s', api_version='v1', kind='Namespace') }}"
|
||||
|
||||
- name: Fetch all deployments
|
||||
set_fact:
|
||||
deployments: "{{ lookup('k8s', kind='Deployment') }}"
|
||||
deployments: "{{ lookup('community.kubernetes.k8s', kind='Deployment') }}"
|
||||
|
||||
- name: Fetch all deployments in a namespace
|
||||
set_fact:
|
||||
deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing') }}"
|
||||
deployments: "{{ lookup('community.kubernetes.k8s', kind='Deployment', namespace='testing') }}"
|
||||
|
||||
- name: Fetch a specific deployment by name
|
||||
set_fact:
|
||||
deployments: "{{ lookup('k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
deployments: "{{ lookup('community.kubernetes.k8s', kind='Deployment', namespace='testing', resource_name='elastic') }}"
|
||||
|
||||
- name: Fetch with label selector
|
||||
set_fact:
|
||||
service: "{{ lookup('k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
service: "{{ lookup('community.kubernetes.k8s', kind='Service', label_selector='app=galaxy') }}"
|
||||
|
||||
# Use parameters from a YAML config
|
||||
|
||||
@@ -159,11 +159,11 @@ EXAMPLES = """
|
||||
|
||||
- name: Using the config (loaded from a file in prior task), fetch the latest version of the object
|
||||
set_fact:
|
||||
service: "{{ lookup('k8s', resource_definition=config) }}"
|
||||
service: "{{ lookup('community.kubernetes.k8s', resource_definition=config) }}"
|
||||
|
||||
- name: Use a config from the local filesystem
|
||||
set_fact:
|
||||
service: "{{ lookup('k8s', src='service.yml') }}"
|
||||
service: "{{ lookup('community.kubernetes.k8s', src='service.yml') }}"
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
@@ -194,15 +194,14 @@ RETURN = """
|
||||
type: complex
|
||||
"""
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.common._collections_compat import KeysView
|
||||
from ansible.plugins.lookup import LookupBase
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import K8sAnsibleMixin
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
|
||||
|
||||
try:
|
||||
from openshift.dynamic import DynamicClient
|
||||
from openshift.dynamic.exceptions import NotFoundError
|
||||
HAS_K8S_MODULE_HELPER = True
|
||||
k8s_import_exception = None
|
||||
@@ -210,12 +209,6 @@ except ImportError as e:
|
||||
HAS_K8S_MODULE_HELPER = False
|
||||
k8s_import_exception = e
|
||||
|
||||
try:
|
||||
import yaml
|
||||
HAS_YAML = True
|
||||
except ImportError:
|
||||
HAS_YAML = False
|
||||
|
||||
|
||||
class KubernetesLookup(K8sAnsibleMixin):
|
||||
|
||||
@@ -226,11 +219,6 @@ class KubernetesLookup(K8sAnsibleMixin):
|
||||
"Requires the OpenShift Python client. Try `pip install openshift`. Detail: {0}".format(k8s_import_exception)
|
||||
)
|
||||
|
||||
if not HAS_YAML:
|
||||
raise Exception(
|
||||
"Requires PyYAML. Try `pip install PyYAML`"
|
||||
)
|
||||
|
||||
self.kind = None
|
||||
self.name = None
|
||||
self.namespace = None
|
||||
@@ -253,6 +241,8 @@ class KubernetesLookup(K8sAnsibleMixin):
|
||||
if cluster_info == 'version':
|
||||
return [self.client.version]
|
||||
if cluster_info == 'api_groups':
|
||||
if isinstance(self.client.resources.api_groups, KeysView):
|
||||
return [list(self.client.resources.api_groups)]
|
||||
return [self.client.resources.api_groups]
|
||||
|
||||
self.kind = kwargs.get('kind')
|
||||
|
||||
@@ -18,14 +18,13 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
import json
|
||||
from datetime import datetime
|
||||
import time
|
||||
import os
|
||||
import traceback
|
||||
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
@@ -34,7 +33,7 @@ try:
|
||||
import kubernetes
|
||||
import openshift
|
||||
from openshift.dynamic import DynamicClient
|
||||
from openshift.dynamic.exceptions import ResourceNotFoundError, ResourceNotUniqueError
|
||||
from openshift.dynamic.exceptions import ResourceNotFoundError, ResourceNotUniqueError, NotFoundError
|
||||
HAS_K8S_MODULE_HELPER = True
|
||||
k8s_import_exception = None
|
||||
except ImportError as e:
|
||||
@@ -56,13 +55,14 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from openshift.dynamic.apply import recursive_diff
|
||||
except ImportError:
|
||||
from ansible.module_utils.common.dict_transformations import recursive_diff
|
||||
|
||||
|
||||
def list_dict_str(value):
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
elif isinstance(value, dict):
|
||||
return value
|
||||
elif isinstance(value, string_types):
|
||||
if isinstance(value, (list, dict, string_types)):
|
||||
return value
|
||||
raise TypeError
|
||||
|
||||
@@ -78,6 +78,9 @@ COMMON_ARG_SPEC = {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
},
|
||||
}
|
||||
|
||||
RESOURCE_ARG_SPEC = {
|
||||
'resource_definition': {
|
||||
'type': list_dict_str,
|
||||
'aliases': ['definition', 'inline']
|
||||
@@ -85,6 +88,9 @@ COMMON_ARG_SPEC = {
|
||||
'src': {
|
||||
'type': 'path',
|
||||
},
|
||||
}
|
||||
|
||||
NAME_ARG_SPEC = {
|
||||
'kind': {},
|
||||
'name': {},
|
||||
'namespace': {},
|
||||
@@ -149,20 +155,15 @@ AUTH_ARG_MAP = {
|
||||
|
||||
|
||||
class K8sAnsibleMixin(object):
|
||||
_argspec_cache = None
|
||||
|
||||
@property
|
||||
def argspec(self):
|
||||
"""
|
||||
Introspect the model properties, and return an Ansible module arg_spec dict.
|
||||
:return: dict
|
||||
"""
|
||||
if self._argspec_cache:
|
||||
return self._argspec_cache
|
||||
argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
|
||||
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
|
||||
self._argspec_cache = argument_spec
|
||||
return self._argspec_cache
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not HAS_K8S_MODULE_HELPER:
|
||||
self.fail_json(msg=missing_required_lib('openshift'), exception=K8S_IMP_ERR,
|
||||
error=to_native(k8s_import_exception))
|
||||
self.openshift_version = openshift.__version__
|
||||
|
||||
if not HAS_YAML:
|
||||
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
||||
|
||||
def get_api_client(self, **auth_params):
|
||||
auth_params = auth_params or getattr(self, 'params', {})
|
||||
@@ -186,13 +187,19 @@ class K8sAnsibleMixin(object):
|
||||
# We have enough in the parameters to authenticate, no need to load incluster or kubeconfig
|
||||
pass
|
||||
elif auth_set('kubeconfig') or auth_set('context'):
|
||||
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||
try:
|
||||
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||
except Exception as err:
|
||||
self.fail(msg='Failed to load kubeconfig due to %s' % to_native(err))
|
||||
else:
|
||||
# First try to do incluster config, then kubeconfig
|
||||
try:
|
||||
kubernetes.config.load_incluster_config()
|
||||
except kubernetes.config.ConfigException:
|
||||
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||
try:
|
||||
kubernetes.config.load_kube_config(auth.get('kubeconfig'), auth.get('context'), persist_config=auth.get('persist_config'))
|
||||
except Exception as err:
|
||||
self.fail(msg='Failed to load kubeconfig due to %s' % to_native(err))
|
||||
|
||||
# Override any values in the default configuration with Ansible parameters
|
||||
configuration = kubernetes.client.Configuration()
|
||||
@@ -204,7 +211,10 @@ class K8sAnsibleMixin(object):
|
||||
setattr(configuration, key, value)
|
||||
|
||||
kubernetes.client.Configuration.set_default(configuration)
|
||||
return DynamicClient(kubernetes.client.ApiClient(configuration))
|
||||
try:
|
||||
return DynamicClient(kubernetes.client.ApiClient(configuration))
|
||||
except Exception as err:
|
||||
self.fail(msg='Failed to get client due to %s' % to_native(err))
|
||||
|
||||
def find_resource(self, kind, api_version, fail=False):
|
||||
for attribute in ['kind', 'name', 'singular_name']:
|
||||
@@ -258,36 +268,165 @@ class K8sAnsibleMixin(object):
|
||||
self.fail(msg="Error loading resource_definition: {0}".format(exc))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def diff_objects(existing, new):
|
||||
def diff_objects(self, existing, new):
|
||||
result = dict()
|
||||
diff = recursive_diff(existing, new)
|
||||
if diff:
|
||||
result['before'] = diff[0]
|
||||
result['after'] = diff[1]
|
||||
return not diff, result
|
||||
if not diff:
|
||||
return True, result
|
||||
|
||||
result['before'] = diff[0]
|
||||
result['after'] = diff[1]
|
||||
|
||||
class KubernetesAnsibleModule(AnsibleModule, K8sAnsibleMixin):
|
||||
resource_definition = None
|
||||
api_version = None
|
||||
kind = None
|
||||
# If only metadata.generation and metadata.resourceVersion changed, ignore it
|
||||
ignored_keys = set(['generation', 'resourceVersion'])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if list(result['after'].keys()) != ['metadata'] or list(result['before'].keys()) != ['metadata']:
|
||||
return False, result
|
||||
|
||||
kwargs['argument_spec'] = self.argspec
|
||||
AnsibleModule.__init__(self, *args, **kwargs)
|
||||
if not set(result['after']['metadata'].keys()).issubset(ignored_keys):
|
||||
return False, result
|
||||
if not set(result['before']['metadata'].keys()).issubset(ignored_keys):
|
||||
return False, result
|
||||
|
||||
if not HAS_K8S_MODULE_HELPER:
|
||||
self.fail_json(msg=missing_required_lib('openshift'), exception=K8S_IMP_ERR,
|
||||
error=to_native(k8s_import_exception))
|
||||
self.openshift_version = openshift.__version__
|
||||
if hasattr(self, 'warn'):
|
||||
self.warn('No meaningful diff was generated, but the API may not be idempotent (only metadata.generation or metadata.resourceVersion were changed)')
|
||||
|
||||
if not HAS_YAML:
|
||||
self.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR)
|
||||
|
||||
def execute_module(self):
|
||||
raise NotImplementedError()
|
||||
return True, result
|
||||
|
||||
def fail(self, msg=None):
|
||||
self.fail_json(msg=msg)
|
||||
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state):
|
||||
start = datetime.now()
|
||||
|
||||
def _wait_for_elapsed():
|
||||
return (datetime.now() - start).seconds
|
||||
|
||||
response = None
|
||||
while _wait_for_elapsed() < timeout:
|
||||
try:
|
||||
response = resource.get(name=name, namespace=namespace)
|
||||
if predicate(response):
|
||||
if response:
|
||||
return True, response.to_dict(), _wait_for_elapsed()
|
||||
else:
|
||||
return True, {}, _wait_for_elapsed()
|
||||
time.sleep(sleep)
|
||||
except NotFoundError:
|
||||
if state == 'absent':
|
||||
return True, {}, _wait_for_elapsed()
|
||||
if response:
|
||||
response = response.to_dict()
|
||||
return False, response, _wait_for_elapsed()
|
||||
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None):
|
||||
|
||||
def _deployment_ready(deployment):
|
||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
||||
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
|
||||
# deployment.status.replicas is None is perfectly ok if desired replicas == 0
|
||||
# Scaling up means that we also need to check that we're not in a
|
||||
# situation where status.replicas == status.availableReplicas
|
||||
# but spec.replicas != status.replicas
|
||||
return (deployment.status
|
||||
and deployment.spec.replicas == (deployment.status.replicas or 0)
|
||||
and deployment.status.availableReplicas == deployment.status.replicas
|
||||
and deployment.status.observedGeneration == deployment.metadata.generation
|
||||
and not deployment.status.unavailableReplicas)
|
||||
|
||||
def _pod_ready(pod):
|
||||
return (pod.status and pod.status.containerStatuses is not None
|
||||
and all([container.ready for container in pod.status.containerStatuses]))
|
||||
|
||||
def _daemonset_ready(daemonset):
|
||||
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None
|
||||
and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled
|
||||
and daemonset.status.observedGeneration == daemonset.metadata.generation
|
||||
and not daemonset.status.unavailableReplicas)
|
||||
|
||||
def _custom_condition(resource):
|
||||
if not resource.status or not resource.status.conditions:
|
||||
return False
|
||||
match = [x for x in resource.status.conditions if x.type == condition['type']]
|
||||
if not match:
|
||||
return False
|
||||
# There should never be more than one condition of a specific type
|
||||
match = match[0]
|
||||
if match.status == 'Unknown':
|
||||
if match.status == condition['status']:
|
||||
if 'reason' not in condition:
|
||||
return True
|
||||
if condition['reason']:
|
||||
return match.reason == condition['reason']
|
||||
return False
|
||||
status = True if match.status == 'True' else False
|
||||
if status == condition['status']:
|
||||
if condition.get('reason'):
|
||||
return match.reason == condition['reason']
|
||||
return True
|
||||
return False
|
||||
|
||||
def _resource_absent(resource):
|
||||
return not resource
|
||||
|
||||
waiter = dict(
|
||||
Deployment=_deployment_ready,
|
||||
DaemonSet=_daemonset_ready,
|
||||
Pod=_pod_ready
|
||||
)
|
||||
kind = definition['kind']
|
||||
if state == 'present' and not condition:
|
||||
predicate = waiter.get(kind, lambda x: x)
|
||||
elif state == 'present' and condition:
|
||||
predicate = _custom_condition
|
||||
else:
|
||||
predicate = _resource_absent
|
||||
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
|
||||
|
||||
def set_resource_definitions(self):
|
||||
resource_definition = self.params.get('resource_definition')
|
||||
|
||||
self.resource_definitions = []
|
||||
|
||||
if resource_definition:
|
||||
if isinstance(resource_definition, string_types):
|
||||
try:
|
||||
self.resource_definitions = yaml.safe_load_all(resource_definition)
|
||||
except (IOError, yaml.YAMLError) as exc:
|
||||
self.fail(msg="Error loading resource_definition: {0}".format(exc))
|
||||
elif isinstance(resource_definition, list):
|
||||
self.resource_definitions = resource_definition
|
||||
else:
|
||||
self.resource_definitions = [resource_definition]
|
||||
|
||||
src = self.params.get('src')
|
||||
if src:
|
||||
self.resource_definitions = self.load_resource_definitions(src)
|
||||
try:
|
||||
self.resource_definitions = [item for item in self.resource_definitions if item]
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not resource_definition and not src:
|
||||
implicit_definition = dict(
|
||||
kind=self.kind,
|
||||
apiVersion=self.api_version,
|
||||
metadata=dict(name=self.name)
|
||||
)
|
||||
if self.namespace:
|
||||
implicit_definition['metadata']['namespace'] = self.namespace
|
||||
self.resource_definitions = [implicit_definition]
|
||||
|
||||
|
||||
class KubernetesAnsibleModule(AnsibleModule, K8sAnsibleMixin):
|
||||
# NOTE: This class KubernetesAnsibleModule is deprecated in favor of
|
||||
# class K8sAnsibleMixin and will be removed 2.0.0 release.
|
||||
# Please use K8sAnsibleMixin instead.
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['argument_spec'] = self.argspec
|
||||
AnsibleModule.__init__(self, *args, **kwargs)
|
||||
K8sAnsibleMixin.__init__(self, *args, **kwargs)
|
||||
|
||||
self.warn("class KubernetesAnsibleModule is deprecated"
|
||||
" and will be removed in 2.0.0. Please use K8sAnsibleMixin instead.")
|
||||
|
||||
@@ -20,32 +20,24 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
from datetime import datetime
|
||||
from distutils.version import LooseVersion
|
||||
import time
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import KubernetesAnsibleModule
|
||||
from ansible.module_utils.basic import missing_required_lib, AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.common.dict_transformations import dict_merge
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||
AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC, K8sAnsibleMixin)
|
||||
|
||||
|
||||
try:
|
||||
import yaml
|
||||
from openshift.dynamic.exceptions import DynamicApiError, NotFoundError, ConflictError, ForbiddenError, KubernetesValidateMissing
|
||||
import urllib3
|
||||
except ImportError:
|
||||
# Exceptions handled in common
|
||||
pass
|
||||
|
||||
try:
|
||||
import kubernetes_validate
|
||||
HAS_KUBERNETES_VALIDATE = True
|
||||
except ImportError:
|
||||
HAS_KUBERNETES_VALIDATE = False
|
||||
|
||||
K8S_CONFIG_HASH_IMP_ERR = None
|
||||
try:
|
||||
@@ -63,7 +55,7 @@ except ImportError:
|
||||
HAS_K8S_APPLY = False
|
||||
|
||||
|
||||
class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
class KubernetesRawModule(K8sAnsibleMixin):
|
||||
|
||||
@property
|
||||
def validate_spec(self):
|
||||
@@ -84,6 +76,8 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
@property
|
||||
def argspec(self):
|
||||
argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
|
||||
argument_spec.update(copy.deepcopy(NAME_ARG_SPEC))
|
||||
argument_spec.update(copy.deepcopy(RESOURCE_ARG_SPEC))
|
||||
argument_spec.update(copy.deepcopy(AUTH_ARG_SPEC))
|
||||
argument_spec['merge_type'] = dict(type='list', elements='str', choices=['json', 'merge', 'strategic-merge'])
|
||||
argument_spec['wait'] = dict(type='bool', default=False)
|
||||
@@ -104,15 +98,25 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
('merge_type', 'apply'),
|
||||
]
|
||||
|
||||
KubernetesAnsibleModule.__init__(self, *args,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
**kwargs)
|
||||
module = AnsibleModule(
|
||||
argument_spec=self.argspec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
self.module = module
|
||||
self.check_mode = self.module.check_mode
|
||||
self.params = self.module.params
|
||||
self.fail_json = self.module.fail_json
|
||||
self.fail = self.module.fail_json
|
||||
self.exit_json = self.module.exit_json
|
||||
|
||||
super(KubernetesRawModule, self).__init__()
|
||||
|
||||
self.kind = k8s_kind or self.params.get('kind')
|
||||
self.api_version = self.params.get('api_version')
|
||||
self.name = self.params.get('name')
|
||||
self.namespace = self.params.get('namespace')
|
||||
resource_definition = self.params.get('resource_definition')
|
||||
validate = self.params.get('validate')
|
||||
if validate:
|
||||
if LooseVersion(self.openshift_version) < LooseVersion("0.8.0"):
|
||||
@@ -129,34 +133,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
if self.apply:
|
||||
if not HAS_K8S_APPLY:
|
||||
self.fail_json(msg=missing_required_lib("openshift >= 0.9.2", reason="for apply"))
|
||||
|
||||
if resource_definition:
|
||||
if isinstance(resource_definition, string_types):
|
||||
try:
|
||||
self.resource_definitions = yaml.safe_load_all(resource_definition)
|
||||
except (IOError, yaml.YAMLError) as exc:
|
||||
self.fail(msg="Error loading resource_definition: {0}".format(exc))
|
||||
elif isinstance(resource_definition, list):
|
||||
self.resource_definitions = resource_definition
|
||||
else:
|
||||
self.resource_definitions = [resource_definition]
|
||||
src = self.params.get('src')
|
||||
if src:
|
||||
self.resource_definitions = self.load_resource_definitions(src)
|
||||
try:
|
||||
self.resource_definitions = [item for item in self.resource_definitions if item]
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not resource_definition and not src:
|
||||
implicit_definition = dict(
|
||||
kind=self.kind,
|
||||
apiVersion=self.api_version,
|
||||
metadata=dict(name=self.name)
|
||||
)
|
||||
if self.namespace:
|
||||
implicit_definition['metadata']['namespace'] = self.namespace
|
||||
self.resource_definitions = [implicit_definition]
|
||||
self.set_resource_definitions()
|
||||
|
||||
def flatten_list_kind(self, list_resource, definitions):
|
||||
flattened = []
|
||||
@@ -178,9 +155,11 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
|
||||
flattened_definitions = []
|
||||
for definition in self.resource_definitions:
|
||||
if definition is None:
|
||||
continue
|
||||
kind = definition.get('kind', self.kind)
|
||||
api_version = definition.get('apiVersion', self.api_version)
|
||||
if kind.endswith('List'):
|
||||
if kind and kind.endswith('List'):
|
||||
resource = self.find_resource(kind, api_version, fail=False)
|
||||
flattened_definitions.extend(self.flatten_list_kind(resource, definition))
|
||||
else:
|
||||
@@ -274,6 +253,9 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
except DynamicApiError as exc:
|
||||
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc.body),
|
||||
error=exc.status, status=exc.status, reason=exc.reason)
|
||||
except Exception as exc:
|
||||
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(to_native(exc)),
|
||||
error='', status='', reason='')
|
||||
|
||||
if state == 'absent':
|
||||
result['method'] = "delete"
|
||||
@@ -299,7 +281,11 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
else:
|
||||
if self.apply:
|
||||
if self.check_mode:
|
||||
ignored, k8s_obj = apply_object(resource, definition)
|
||||
ignored, patch = apply_object(resource, definition)
|
||||
if existing:
|
||||
k8s_obj = dict_merge(existing.to_dict(), patch)
|
||||
else:
|
||||
k8s_obj = patch
|
||||
else:
|
||||
try:
|
||||
k8s_obj = resource.apply(definition, namespace=namespace).to_dict()
|
||||
@@ -310,7 +296,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
self.fail_json(msg=msg, error=exc.status, status=exc.status, reason=exc.reason)
|
||||
success = True
|
||||
result['result'] = k8s_obj
|
||||
if wait:
|
||||
if wait and not self.check_mode:
|
||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
if existing:
|
||||
existing = existing.to_dict()
|
||||
@@ -369,7 +355,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
|
||||
success = True
|
||||
result['result'] = k8s_obj
|
||||
if wait:
|
||||
if wait and not self.check_mode:
|
||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
||||
result['changed'] = not match
|
||||
@@ -397,7 +383,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
|
||||
success = True
|
||||
result['result'] = k8s_obj
|
||||
if wait:
|
||||
if wait and not self.check_mode:
|
||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_sleep, wait_timeout, condition=wait_condition)
|
||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
||||
result['changed'] = not match
|
||||
@@ -428,6 +414,12 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
msg += "\n" + "\n ".join(self.warnings)
|
||||
error = dict(msg=msg, error=exc.status, status=exc.status, reason=exc.reason, warnings=self.warnings)
|
||||
return None, error
|
||||
except Exception as exc:
|
||||
msg = "Failed to patch object: {0}".format(exc)
|
||||
if self.warnings:
|
||||
msg += "\n" + "\n ".join(self.warnings)
|
||||
error = dict(msg=msg, error=to_native(exc), status='', reason='', warnings=self.warnings)
|
||||
return None, error
|
||||
|
||||
def create_project_request(self, definition):
|
||||
definition['kind'] = 'ProjectRequest'
|
||||
@@ -443,83 +435,3 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||
result['changed'] = True
|
||||
result['method'] = 'create'
|
||||
return result
|
||||
|
||||
def _wait_for(self, resource, name, namespace, predicate, sleep, timeout, state):
|
||||
start = datetime.now()
|
||||
|
||||
def _wait_for_elapsed():
|
||||
return (datetime.now() - start).seconds
|
||||
|
||||
response = None
|
||||
while _wait_for_elapsed() < timeout:
|
||||
try:
|
||||
response = resource.get(name=name, namespace=namespace)
|
||||
if predicate(response):
|
||||
if response:
|
||||
return True, response.to_dict(), _wait_for_elapsed()
|
||||
else:
|
||||
return True, {}, _wait_for_elapsed()
|
||||
time.sleep(sleep)
|
||||
except NotFoundError:
|
||||
if state == 'absent':
|
||||
return True, {}, _wait_for_elapsed()
|
||||
if response:
|
||||
response = response.to_dict()
|
||||
return False, response, _wait_for_elapsed()
|
||||
|
||||
def wait(self, resource, definition, sleep, timeout, state='present', condition=None):
|
||||
|
||||
def _deployment_ready(deployment):
|
||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
||||
# Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty
|
||||
return (deployment.status and deployment.status.replicas is not None and
|
||||
deployment.status.availableReplicas == deployment.status.replicas and
|
||||
deployment.status.observedGeneration == deployment.metadata.generation)
|
||||
|
||||
def _pod_ready(pod):
|
||||
return (pod.status and pod.status.containerStatuses is not None and
|
||||
all([container.ready for container in pod.status.containerStatuses]))
|
||||
|
||||
def _daemonset_ready(daemonset):
|
||||
return (daemonset.status and daemonset.status.desiredNumberScheduled is not None and
|
||||
daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and
|
||||
daemonset.status.observedGeneration == daemonset.metadata.generation)
|
||||
|
||||
def _custom_condition(resource):
|
||||
if not resource.status or not resource.status.conditions:
|
||||
return False
|
||||
match = [x for x in resource.status.conditions if x.type == condition['type']]
|
||||
if not match:
|
||||
return False
|
||||
# There should never be more than one condition of a specific type
|
||||
match = match[0]
|
||||
if match.status == 'Unknown':
|
||||
if match.status == condition['status']:
|
||||
if 'reason' not in condition:
|
||||
return True
|
||||
if condition['reason']:
|
||||
return match.reason == condition['reason']
|
||||
return False
|
||||
status = True if match.status == 'True' else False
|
||||
if status == condition['status']:
|
||||
if condition.get('reason'):
|
||||
return match.reason == condition['reason']
|
||||
return True
|
||||
return False
|
||||
|
||||
def _resource_absent(resource):
|
||||
return not resource
|
||||
|
||||
waiter = dict(
|
||||
Deployment=_deployment_ready,
|
||||
DaemonSet=_daemonset_ready,
|
||||
Pod=_pod_ready
|
||||
)
|
||||
kind = definition['kind']
|
||||
if state == 'present' and not condition:
|
||||
predicate = waiter.get(kind, lambda x: x)
|
||||
elif state == 'present' and condition:
|
||||
predicate = _custom_condition
|
||||
else:
|
||||
predicate = _resource_absent
|
||||
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, sleep, timeout, state)
|
||||
|
||||
@@ -20,21 +20,15 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
import math
|
||||
import time
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import KubernetesAnsibleModule
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||
AUTH_ARG_SPEC, RESOURCE_ARG_SPEC, NAME_ARG_SPEC, K8sAnsibleMixin)
|
||||
|
||||
try:
|
||||
import yaml
|
||||
from openshift import watch
|
||||
from openshift.dynamic.client import ResourceInstance
|
||||
from openshift.helper.exceptions import KubernetesException
|
||||
except ImportError as exc:
|
||||
class KubernetesException(Exception):
|
||||
pass
|
||||
from openshift.dynamic.exceptions import NotFoundError
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
SCALE_ARG_SPEC = {
|
||||
@@ -46,7 +40,7 @@ SCALE_ARG_SPEC = {
|
||||
}
|
||||
|
||||
|
||||
class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
||||
class KubernetesAnsibleScaleModule(K8sAnsibleMixin):
|
||||
|
||||
def __init__(self, k8s_kind=None, *args, **kwargs):
|
||||
self.client = None
|
||||
@@ -56,39 +50,25 @@ class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
||||
('resource_definition', 'src'),
|
||||
]
|
||||
|
||||
KubernetesAnsibleModule.__init__(self, *args,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
**kwargs)
|
||||
module = AnsibleModule(
|
||||
argument_spec=self.argspec,
|
||||
mutually_exclusive=mutually_exclusive,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
self.module = module
|
||||
self.params = self.module.params
|
||||
self.check_mode = self.module.check_mode
|
||||
self.fail_json = self.module.fail_json
|
||||
self.fail = self.module.fail_json
|
||||
self.exit_json = self.module.exit_json
|
||||
super(KubernetesAnsibleScaleModule, self).__init__()
|
||||
|
||||
self.kind = k8s_kind or self.params.get('kind')
|
||||
self.api_version = self.params.get('api_version')
|
||||
self.name = self.params.get('name')
|
||||
self.namespace = self.params.get('namespace')
|
||||
resource_definition = self.params.get('resource_definition')
|
||||
|
||||
if resource_definition:
|
||||
if isinstance(resource_definition, string_types):
|
||||
try:
|
||||
self.resource_definitions = yaml.safe_load_all(resource_definition)
|
||||
except (IOError, yaml.YAMLError) as exc:
|
||||
self.fail(msg="Error loading resource_definition: {0}".format(exc))
|
||||
elif isinstance(resource_definition, list):
|
||||
self.resource_definitions = resource_definition
|
||||
else:
|
||||
self.resource_definitions = [resource_definition]
|
||||
src = self.params.get('src')
|
||||
if src:
|
||||
self.resource_definitions = self.load_resource_definitions(src)
|
||||
|
||||
if not resource_definition and not src:
|
||||
implicit_definition = dict(
|
||||
kind=self.kind,
|
||||
apiVersion=self.api_version,
|
||||
metadata=dict(name=self.name)
|
||||
)
|
||||
if self.namespace:
|
||||
implicit_definition['metadata']['namespace'] = self.namespace
|
||||
self.resource_definitions = [implicit_definition]
|
||||
self.set_resource_definitions()
|
||||
|
||||
def execute_module(self):
|
||||
definition = self.resource_definitions[0]
|
||||
@@ -107,14 +87,16 @@ class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
||||
wait_time = self.params.get('wait_timeout')
|
||||
existing = None
|
||||
existing_count = None
|
||||
return_attributes = dict(changed=False, result=dict())
|
||||
return_attributes = dict(changed=False, result=dict(), diff=dict())
|
||||
if wait:
|
||||
return_attributes['duration'] = 0
|
||||
|
||||
resource = self.find_resource(kind, api_version, fail=True)
|
||||
|
||||
try:
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
return_attributes['result'] = existing.to_dict()
|
||||
except KubernetesException as exc:
|
||||
except NotFoundError as exc:
|
||||
self.fail_json(msg='Failed to retrieve requested object: {0}'.format(exc),
|
||||
error=exc.value.get('status'))
|
||||
|
||||
@@ -137,108 +119,48 @@ class KubernetesAnsibleScaleModule(KubernetesAnsibleModule):
|
||||
if not self.check_mode:
|
||||
if self.kind == 'job':
|
||||
existing.spec.parallelism = replicas
|
||||
k8s_obj = resource.patch(existing.to_dict())
|
||||
return_attributes['result'] = resource.patch(existing.to_dict()).to_dict()
|
||||
else:
|
||||
k8s_obj = self.scale(resource, existing, replicas, wait, wait_time)
|
||||
return_attributes['result'] = k8s_obj.to_dict()
|
||||
return_attributes = self.scale(resource, existing, replicas, wait, wait_time)
|
||||
|
||||
self.exit_json(**return_attributes)
|
||||
|
||||
@property
|
||||
def argspec(self):
|
||||
args = copy.deepcopy(COMMON_ARG_SPEC)
|
||||
args.pop('state')
|
||||
args.pop('force')
|
||||
args = copy.deepcopy(SCALE_ARG_SPEC)
|
||||
args.update(RESOURCE_ARG_SPEC)
|
||||
args.update(NAME_ARG_SPEC)
|
||||
args.update(AUTH_ARG_SPEC)
|
||||
args.update(SCALE_ARG_SPEC)
|
||||
return args
|
||||
|
||||
def scale(self, resource, existing_object, replicas, wait, wait_time):
|
||||
name = existing_object.metadata.name
|
||||
namespace = existing_object.metadata.namespace
|
||||
kind = existing_object.kind
|
||||
|
||||
if not hasattr(resource, 'scale'):
|
||||
self.fail_json(
|
||||
msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
|
||||
)
|
||||
|
||||
scale_obj = {'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
|
||||
scale_obj = {'kind': kind, 'metadata': {'name': name, 'namespace': namespace}, 'spec': {'replicas': replicas}}
|
||||
|
||||
return_obj = None
|
||||
stream = None
|
||||
|
||||
if wait:
|
||||
w, stream = self._create_stream(resource, namespace, wait_time)
|
||||
existing = resource.get(name=name, namespace=namespace)
|
||||
|
||||
try:
|
||||
resource.scale.patch(body=scale_obj)
|
||||
except Exception as exc:
|
||||
self.fail_json(
|
||||
msg="Scale request failed: {0}".format(exc)
|
||||
)
|
||||
self.fail_json(msg="Scale request failed: {0}".format(exc))
|
||||
|
||||
if wait and stream is not None:
|
||||
return_obj = self._read_stream(resource, w, stream, name, replicas)
|
||||
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
|
||||
match, diffs = self.diff_objects(existing.to_dict(), k8s_obj)
|
||||
result = dict()
|
||||
result['result'] = k8s_obj
|
||||
result['changed'] = not match
|
||||
result['diff'] = diffs
|
||||
|
||||
if not return_obj:
|
||||
return_obj = self._wait_for_response(resource, name, namespace)
|
||||
|
||||
return return_obj
|
||||
|
||||
def _create_stream(self, resource, namespace, wait_time):
|
||||
""" Create a stream of events for the object """
|
||||
w = None
|
||||
stream = None
|
||||
try:
|
||||
w = watch.Watch()
|
||||
w._api_client = self.client.client
|
||||
if namespace:
|
||||
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
||||
else:
|
||||
stream = w.stream(resource.get, serialize=False, namespace=namespace, timeout_seconds=wait_time)
|
||||
except KubernetesException:
|
||||
pass
|
||||
return w, stream
|
||||
|
||||
def _read_stream(self, resource, watcher, stream, name, replicas):
|
||||
""" Wait for ready_replicas to equal the requested number of replicas. """
|
||||
return_obj = None
|
||||
try:
|
||||
for event in stream:
|
||||
if event.get('object'):
|
||||
obj = ResourceInstance(resource, event['object'])
|
||||
if obj.metadata.name == name and hasattr(obj, 'status'):
|
||||
if replicas == 0:
|
||||
if not hasattr(obj.status, 'readyReplicas') or not obj.status.readyReplicas:
|
||||
return_obj = obj
|
||||
watcher.stop()
|
||||
break
|
||||
if hasattr(obj.status, 'readyReplicas') and obj.status.readyReplicas == replicas:
|
||||
return_obj = obj
|
||||
watcher.stop()
|
||||
break
|
||||
except Exception as exc:
|
||||
self.fail_json(msg="Exception reading event stream: {0}".format(exc))
|
||||
|
||||
if not return_obj:
|
||||
self.fail_json(msg="Error fetching the patched object. Try a higher wait_timeout value.")
|
||||
if replicas and return_obj.status.readyReplicas is None:
|
||||
self.fail_json(msg="Failed to fetch the number of ready replicas. Try a higher wait_timeout value.")
|
||||
if replicas and return_obj.status.readyReplicas != replicas:
|
||||
self.fail_json(msg="Number of ready replicas is {0}. Failed to reach {1} ready replicas within "
|
||||
"the wait_timeout period.".format(return_obj.status.ready_replicas, replicas))
|
||||
return return_obj
|
||||
|
||||
def _wait_for_response(self, resource, name, namespace):
|
||||
""" Wait for an API response """
|
||||
tries = 0
|
||||
half = math.ceil(20 / 2)
|
||||
obj = None
|
||||
|
||||
while tries <= half:
|
||||
obj = resource.get(name=name, namespace=namespace)
|
||||
if obj:
|
||||
break
|
||||
tries += 2
|
||||
time.sleep(2)
|
||||
return obj
|
||||
if wait:
|
||||
success, result['result'], result['duration'] = self.wait(resource, scale_obj, 5, wait_time)
|
||||
if not success:
|
||||
self.fail_json(msg="Resource scaling timed out", **result)
|
||||
return result
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: helm
|
||||
|
||||
short_description: Manages Kubernetes packages with the Helm package manager
|
||||
|
||||
version_added: "0.11.0"
|
||||
|
||||
author:
|
||||
- Lucas Boisserie (@LucasBoisserie)
|
||||
- Matthieu Diehr (@d-matt)
|
||||
@@ -28,11 +27,6 @@ description:
|
||||
- Install, upgrade, delete packages with the Helm package manager.
|
||||
|
||||
options:
|
||||
binary_path:
|
||||
description:
|
||||
- The path of a helm binary to use.
|
||||
required: false
|
||||
type: path
|
||||
chart_ref:
|
||||
description:
|
||||
- chart_reference on chart repository.
|
||||
@@ -96,15 +90,6 @@ options:
|
||||
- Helm option to force reinstall, ignore on new install.
|
||||
default: False
|
||||
type: bool
|
||||
kube_context:
|
||||
description:
|
||||
- Helm option to specify which kubeconfig context to use.
|
||||
type: str
|
||||
kubeconfig_path:
|
||||
description:
|
||||
- Helm option to specify kubeconfig path to use.
|
||||
type: path
|
||||
aliases: [ kubeconfig ]
|
||||
purge:
|
||||
description:
|
||||
- Remove the release from the store and make its name free for later use.
|
||||
@@ -119,24 +104,44 @@ options:
|
||||
description:
|
||||
- Timeout when wait option is enabled (helm2 is a number of seconds, helm3 is a duration).
|
||||
type: str
|
||||
atomic:
|
||||
description:
|
||||
- If set, the installation process deletes the installation on failure.
|
||||
type: bool
|
||||
default: False
|
||||
create_namespace:
|
||||
description:
|
||||
- Create the release namespace if not present.
|
||||
type: bool
|
||||
default: False
|
||||
version_added: "0.11.1"
|
||||
replace:
|
||||
description:
|
||||
- Reuse the given name, only if that name is a deleted release which remains in the history.
|
||||
- This is unsafe in production environment.
|
||||
type: bool
|
||||
default: False
|
||||
version_added: "1.11.0"
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.helm_common_options
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create helm namespace as HELM 3 doesn't create it automatically
|
||||
k8s:
|
||||
api_version: v1
|
||||
kind: Namespace
|
||||
name: "monitoring"
|
||||
wait: true
|
||||
EXAMPLES = r'''
|
||||
- name: Deploy latest version of Prometheus chart inside monitoring namespace (and create it)
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
chart_ref: stable/prometheus
|
||||
release_namespace: monitoring
|
||||
create_namespace: true
|
||||
|
||||
# From repository
|
||||
- name: Add stable chart repo
|
||||
helm_repository:
|
||||
community.kubernetes.helm_repository:
|
||||
name: stable
|
||||
repo_url: "https://kubernetes-charts.storage.googleapis.com"
|
||||
|
||||
- name: Deploy latest version of Grafana chart inside monitoring namespace with values
|
||||
helm:
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
chart_ref: stable/grafana
|
||||
release_namespace: monitoring
|
||||
@@ -144,39 +149,39 @@ EXAMPLES = '''
|
||||
replicas: 2
|
||||
|
||||
- name: Deploy Grafana chart on 5.0.12 with values loaded from template
|
||||
helm:
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
chart_ref: stable/grafana
|
||||
chart_version: 5.0.12
|
||||
values: "{{ lookup('template', 'somefile.yaml') | from_yaml }}"
|
||||
|
||||
- name: Remove test release and waiting suppression ending
|
||||
helm:
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
state: absent
|
||||
wait: true
|
||||
|
||||
# From git
|
||||
- name: Git clone stable repo on HEAD
|
||||
git:
|
||||
ansible.builtin.git:
|
||||
repo: "http://github.com/helm/charts.git"
|
||||
dest: /tmp/helm_repo
|
||||
|
||||
- name: Deploy Grafana chart from local path
|
||||
helm:
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
chart_ref: /tmp/helm_repo/stable/grafana
|
||||
release_namespace: monitoring
|
||||
|
||||
# From url
|
||||
- name: Deploy Grafana chart on 5.0.12 from url
|
||||
helm:
|
||||
community.kubernetes.helm:
|
||||
name: test
|
||||
chart_ref: "https://kubernetes-charts.storage.googleapis.com/grafana-5.0.12.tgz"
|
||||
release_namespace: monitoring
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
RETURN = r"""
|
||||
status:
|
||||
type: complex
|
||||
description: A dictionary of status output
|
||||
@@ -231,6 +236,7 @@ command:
|
||||
sample: helm upgrade ...
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
try:
|
||||
@@ -240,32 +246,42 @@ except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
IMP_YAML = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||
|
||||
module = None
|
||||
|
||||
|
||||
# Get Values from deployed release
|
||||
def get_values(command, release_name):
|
||||
get_command = command + " get values --output=yaml " + release_name
|
||||
|
||||
rc, out, err = module.run_command(get_command)
|
||||
|
||||
def exec_command(command):
|
||||
rc, out, err = module.run_command(command)
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=get_command
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
command=command,
|
||||
)
|
||||
return rc, out, err
|
||||
|
||||
|
||||
def get_values(command, release_name):
|
||||
"""
|
||||
Get Values from deployed release
|
||||
"""
|
||||
|
||||
get_command = command + " get values --output=yaml " + release_name
|
||||
|
||||
rc, out, err = exec_command(get_command)
|
||||
# Helm 3 return "null" string when no values are set
|
||||
if out.rstrip("\n") == "null":
|
||||
return {}
|
||||
else:
|
||||
return yaml.safe_load(out)
|
||||
return yaml.safe_load(out)
|
||||
|
||||
|
||||
# Get Release from all deployed releases
|
||||
def get_release(state, release_name):
|
||||
"""
|
||||
Get Release from all deployed releases
|
||||
"""
|
||||
|
||||
if state is not None:
|
||||
for release in state:
|
||||
if release['name'] == release_name:
|
||||
@@ -273,17 +289,14 @@ def get_release(state, release_name):
|
||||
return None
|
||||
|
||||
|
||||
# Get Release state from deployed release
|
||||
def get_release_status(command, release_name):
|
||||
"""
|
||||
Get Release state from deployed release
|
||||
"""
|
||||
|
||||
list_command = command + " list --output=yaml --filter " + release_name
|
||||
|
||||
rc, out, err = module.run_command(list_command)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=list_command
|
||||
)
|
||||
rc, out, err = exec_command(list_command)
|
||||
|
||||
release = get_release(yaml.safe_load(out), release_name)
|
||||
|
||||
@@ -295,56 +308,61 @@ def get_release_status(command, release_name):
|
||||
return release
|
||||
|
||||
|
||||
# Run Repo update
|
||||
def run_repo_update(command):
|
||||
"""
|
||||
Run Repo update
|
||||
"""
|
||||
repo_update_command = command + " repo update"
|
||||
|
||||
rc, out, err = module.run_command(repo_update_command)
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=repo_update_command
|
||||
)
|
||||
rc, out, err = exec_command(repo_update_command)
|
||||
|
||||
|
||||
# Get chart info
|
||||
def fetch_chart_info(command, chart_ref):
|
||||
"""
|
||||
Get chart info
|
||||
"""
|
||||
inspect_command = command + " show chart " + chart_ref
|
||||
|
||||
rc, out, err = module.run_command(inspect_command)
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=inspect_command
|
||||
)
|
||||
rc, out, err = exec_command(inspect_command)
|
||||
|
||||
return yaml.safe_load(out)
|
||||
|
||||
|
||||
# Install/upgrade/rollback release chart
|
||||
def deploy(command, release_name, release_values, chart_name, wait, wait_timeout, disable_hook, force):
|
||||
deploy_command = command + " upgrade -i" # install/upgrade
|
||||
def deploy(command, release_name, release_values, chart_name, wait,
|
||||
wait_timeout, disable_hook, force, atomic=False, create_namespace=False,
|
||||
replace=False):
|
||||
"""
|
||||
Install/upgrade/rollback release chart
|
||||
"""
|
||||
if replace:
|
||||
# '--replace' is not supported by 'upgrade -i'
|
||||
deploy_command = command + " install"
|
||||
else:
|
||||
deploy_command = command + " upgrade -i" # install/upgrade
|
||||
|
||||
# Always reset values to keep release_values equal to values released
|
||||
deploy_command += " --reset-values"
|
||||
# Always reset values to keep release_values equal to values released
|
||||
deploy_command += " --reset-values"
|
||||
|
||||
if wait:
|
||||
deploy_command += " --wait"
|
||||
if wait_timeout is not None:
|
||||
deploy_command += " --timeout " + wait_timeout
|
||||
|
||||
if atomic:
|
||||
deploy_command += " --atomic"
|
||||
|
||||
if force:
|
||||
deploy_command += " --force"
|
||||
|
||||
if replace:
|
||||
deploy_command += " --replace"
|
||||
|
||||
if disable_hook:
|
||||
deploy_command += " --no-hooks"
|
||||
|
||||
if release_values != {}:
|
||||
try:
|
||||
import tempfile
|
||||
except ImportError:
|
||||
module.fail_json(msg=missing_required_lib("tempfile"), exception=traceback.format_exc())
|
||||
if create_namespace:
|
||||
deploy_command += " --create-namespace"
|
||||
|
||||
if release_values != {}:
|
||||
fd, path = tempfile.mkstemp(suffix='.yml')
|
||||
with open(path, 'w') as yaml_file:
|
||||
yaml.dump(release_values, yaml_file, default_flow_style=False)
|
||||
@@ -355,8 +373,11 @@ def deploy(command, release_name, release_values, chart_name, wait, wait_timeout
|
||||
return deploy_command
|
||||
|
||||
|
||||
# Delete release chart
|
||||
def delete(command, release_name, purge, disable_hook):
|
||||
"""
|
||||
Delete release chart
|
||||
"""
|
||||
|
||||
delete_command = command + " uninstall "
|
||||
|
||||
if not purge:
|
||||
@@ -387,11 +408,14 @@ def main():
|
||||
# Helm options
|
||||
disable_hook=dict(type='bool', default=False),
|
||||
force=dict(type='bool', default=False),
|
||||
kube_context=dict(type='str'),
|
||||
kubeconfig_path=dict(type='path', aliases=['kubeconfig']),
|
||||
kube_context=dict(type='str', aliases=['context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig_path=dict(type='path', aliases=['kubeconfig'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
purge=dict(type='bool', default=True),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='str'),
|
||||
atomic=dict(type='bool', default=False),
|
||||
create_namespace=dict(type='bool', default=False),
|
||||
replace=dict(type='bool', default=False),
|
||||
),
|
||||
required_if=[
|
||||
('release_state', 'present', ['release_name', 'chart_ref']),
|
||||
@@ -423,6 +447,9 @@ def main():
|
||||
purge = module.params.get('purge')
|
||||
wait = module.params.get('wait')
|
||||
wait_timeout = module.params.get('wait_timeout')
|
||||
atomic = module.params.get('atomic')
|
||||
create_namespace = module.params.get('create_namespace')
|
||||
replace = module.params.get('replace')
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
@@ -446,6 +473,9 @@ def main():
|
||||
# keep helm_cmd_common for get_release_status in module_exit_json
|
||||
helm_cmd = helm_cmd_common
|
||||
if release_state == "absent" and release_status is not None:
|
||||
if replace:
|
||||
module.fail_json(msg="replace is not applicable when state is absent")
|
||||
|
||||
helm_cmd = delete(helm_cmd, release_name, purge, disable_hook)
|
||||
changed = True
|
||||
elif release_state == "present":
|
||||
@@ -461,30 +491,42 @@ def main():
|
||||
|
||||
if release_status is None: # Not installed
|
||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||
disable_hook, False)
|
||||
disable_hook, False, atomic=atomic, create_namespace=create_namespace,
|
||||
replace=replace)
|
||||
changed = True
|
||||
|
||||
elif force or release_values != release_status['values'] \
|
||||
or (chart_info['name'] + '-' + chart_info['version']) != release_status["chart"]:
|
||||
helm_cmd = deploy(helm_cmd, release_name, release_values, chart_ref, wait, wait_timeout,
|
||||
disable_hook, force)
|
||||
disable_hook, force, atomic=atomic, create_namespace=create_namespace,
|
||||
replace=replace)
|
||||
changed = True
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=changed)
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
command=helm_cmd,
|
||||
stdout='',
|
||||
stderr='',
|
||||
)
|
||||
elif not changed:
|
||||
module.exit_json(changed=False, status=release_status)
|
||||
|
||||
rc, out, err = module.run_command(helm_cmd)
|
||||
|
||||
if rc != 0:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command. Exited {0}.\nstdout: {1}\nstderr: {2}".format(rc, out, err),
|
||||
command=helm_cmd
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
status=release_status,
|
||||
stdout='',
|
||||
stderr='',
|
||||
command=helm_cmd,
|
||||
)
|
||||
|
||||
module.exit_json(changed=changed, stdout=out, stderr=err,
|
||||
status=get_release_status(helm_cmd_common, release_name), command=helm_cmd)
|
||||
rc, out, err = exec_command(helm_cmd)
|
||||
|
||||
module.exit_json(
|
||||
changed=changed,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
status=get_release_status(helm_cmd_common, release_name),
|
||||
command=helm_cmd,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: Ansible Project
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: helm_info
|
||||
|
||||
short_description: Get informations from Helm package deployed inside the cluster
|
||||
short_description: Get information from Helm package deployed inside the cluster
|
||||
|
||||
version_added: "0.11.0"
|
||||
|
||||
author:
|
||||
- Lucas Boisserie (@LucasBoisserie)
|
||||
@@ -24,14 +23,9 @@ requirements:
|
||||
- "yaml (https://pypi.org/project/PyYAML/)"
|
||||
|
||||
description:
|
||||
- Get informations (values, states, ...) from Helm package deployed inside the cluster
|
||||
- Get information (values, states, ...) from Helm package deployed inside the cluster.
|
||||
|
||||
options:
|
||||
binary_path:
|
||||
description:
|
||||
- The path of a helm binary to use.
|
||||
required: false
|
||||
type: path
|
||||
release_name:
|
||||
description:
|
||||
- Release name to manage.
|
||||
@@ -44,27 +38,18 @@ options:
|
||||
required: true
|
||||
type: str
|
||||
aliases: [ namespace ]
|
||||
|
||||
#Helm options
|
||||
kube_context:
|
||||
description:
|
||||
- Helm option to specify which kubeconfig context to use.
|
||||
type: str
|
||||
kubeconfig_path:
|
||||
description:
|
||||
- Helm option to specify kubeconfig path to use.
|
||||
type: path
|
||||
aliases: [ kubeconfig ]
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.helm_common_options
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Deploy latest version of Grafana chart inside monitoring namespace
|
||||
helm_info:
|
||||
community.kubernetes.helm_info:
|
||||
name: test
|
||||
release_namespace: monitoring
|
||||
'''
|
||||
|
||||
RETURN = """
|
||||
RETURN = r'''
|
||||
status:
|
||||
type: complex
|
||||
description: A dictionary of status output
|
||||
@@ -102,7 +87,7 @@ status:
|
||||
type: str
|
||||
returned: always
|
||||
description: Dict of Values used to deploy
|
||||
"""
|
||||
'''
|
||||
|
||||
import traceback
|
||||
|
||||
@@ -113,7 +98,7 @@ except ImportError:
|
||||
IMP_YAML_ERR = traceback.format_exc()
|
||||
IMP_YAML = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib, env_fallback
|
||||
|
||||
module = None
|
||||
|
||||
@@ -178,8 +163,8 @@ def main():
|
||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
||||
|
||||
# Helm options
|
||||
kube_context=dict(type='str'),
|
||||
kubeconfig_path=dict(type='path', aliases=['kubeconfig']),
|
||||
kube_context=dict(type='str', aliases=['context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig_path=dict(type='path', aliases=['kubeconfig'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
@@ -212,8 +197,8 @@ def main():
|
||||
|
||||
if release_status is not None:
|
||||
module.exit_json(changed=False, status=release_status)
|
||||
else:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
module.exit_json(changed=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
242
plugins/modules/helm_plugin.py
Normal file
242
plugins/modules/helm_plugin.py
Normal file
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: helm_plugin
|
||||
short_description: Manage Helm plugins
|
||||
version_added: "1.0.0"
|
||||
author:
|
||||
- Abhijeet Kasurde (@Akasurde)
|
||||
requirements:
|
||||
- "helm (https://github.com/helm/helm/releases)"
|
||||
description:
|
||||
- Install, uninstall Helm plugins.
|
||||
options:
|
||||
release_namespace:
|
||||
description:
|
||||
- Kubernetes namespace where the helm plugin should be installed.
|
||||
required: true
|
||||
type: str
|
||||
aliases: [ namespace ]
|
||||
|
||||
#Helm options
|
||||
state:
|
||||
description:
|
||||
- If C(state=present), Helm plugin will be installed.
|
||||
- If C(state=absent), Helm plugin will be uninstalled.
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
type: str
|
||||
plugin_name:
|
||||
description:
|
||||
- Name of Helm plugin.
|
||||
- Required only if C(state=absent).
|
||||
type: str
|
||||
plugin_path:
|
||||
description:
|
||||
- Plugin path to a plugin on your local file system or a url of a remote VCS repo.
|
||||
- If plugin path from file system is provided, make sure that tar is present on remote
|
||||
machine and not on Ansible controller.
|
||||
- Required only if C(state=present).
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.helm_common_options
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Install Helm env plugin
|
||||
community.kubernetes.helm_plugin:
|
||||
plugin_path: https://github.com/adamreese/helm-env
|
||||
state: present
|
||||
|
||||
- name: Install Helm plugin from local filesystem
|
||||
community.kubernetes.helm_plugin:
|
||||
plugin_path: https://domain/path/to/plugin.tar.gz
|
||||
state: present
|
||||
|
||||
- name: Uninstall Helm env plugin
|
||||
community.kubernetes.helm_plugin:
|
||||
plugin_name: env
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
stdout:
|
||||
type: str
|
||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||
returned: always
|
||||
sample: ''
|
||||
stderr:
|
||||
type: str
|
||||
description: Full `helm` command stderr, in case you want to display it or examine the event log
|
||||
returned: always
|
||||
sample: ''
|
||||
command:
|
||||
type: str
|
||||
description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
|
||||
returned: always
|
||||
sample: helm plugin list ...
|
||||
msg:
|
||||
type: str
|
||||
description: Info about successful command
|
||||
returned: always
|
||||
sample: "Plugin installed successfully"
|
||||
rc:
|
||||
type: int
|
||||
description: Helm plugin command return code
|
||||
returned: always
|
||||
sample: 1
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
plugin_path=dict(type='str',),
|
||||
plugin_name=dict(type='str',),
|
||||
# Helm options
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_if=[
|
||||
("state", "present", ("plugin_path",)),
|
||||
("state", "absent", ("plugin_name",)),
|
||||
],
|
||||
mutually_exclusive=[
|
||||
['plugin_name', 'plugin_path'],
|
||||
],
|
||||
)
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
release_namespace = module.params.get('release_namespace')
|
||||
state = module.params.get('state')
|
||||
|
||||
# Helm options
|
||||
kube_context = module.params.get('context')
|
||||
kubeconfig_path = module.params.get('kubeconfig')
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = 'helm'
|
||||
|
||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||
|
||||
helm_cmd_common += " plugin"
|
||||
|
||||
if kube_context is not None:
|
||||
helm_cmd_common += " --kube-context " + kube_context
|
||||
|
||||
if kubeconfig_path is not None:
|
||||
helm_cmd_common += " --kubeconfig " + kubeconfig_path
|
||||
|
||||
helm_cmd_common += " --namespace=" + release_namespace
|
||||
|
||||
if state == 'present':
|
||||
helm_cmd_common += " install %s" % module.params.get('plugin_path')
|
||||
if not module.check_mode:
|
||||
rc, out, err = module.run_command(helm_cmd_common)
|
||||
else:
|
||||
rc, out, err = (0, '', '')
|
||||
|
||||
if rc == 1 and 'plugin already exists' in err:
|
||||
module.exit_json(
|
||||
failed=False,
|
||||
changed=False,
|
||||
msg="Plugin already exists",
|
||||
command=helm_cmd_common,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
)
|
||||
elif rc == 0:
|
||||
module.exit_json(
|
||||
failed=False,
|
||||
changed=True,
|
||||
msg="Plugin installed successfully",
|
||||
command=helm_cmd_common,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
else:
|
||||
module.fail_json(
|
||||
msg="Failure when executing Helm command.",
|
||||
command=helm_cmd_common,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
elif state == 'absent':
|
||||
plugin_name = module.params.get('plugin_name')
|
||||
helm_plugin_list = helm_cmd_common + " list"
|
||||
rc, out, err = module.run_command(helm_plugin_list)
|
||||
if rc != 0 or (out == '' and err == ''):
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin info",
|
||||
command=helm_plugin_list,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
if out:
|
||||
found = False
|
||||
for line in out.splitlines():
|
||||
if line.startswith("NAME"):
|
||||
continue
|
||||
name, dummy, dummy = line.split('\t', 3)
|
||||
name = name.strip()
|
||||
if name == plugin_name:
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
helm_uninstall_cmd = "%s uninstall %s" % (helm_cmd_common, plugin_name)
|
||||
if not module.check_mode:
|
||||
rc, out, err = module.run_command(helm_uninstall_cmd)
|
||||
else:
|
||||
rc, out, err = (0, '', '')
|
||||
|
||||
if rc == 0:
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
msg="Plugin uninstalled successfully",
|
||||
command=helm_uninstall_cmd,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
)
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin uninstall",
|
||||
command=helm_uninstall_cmd,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
else:
|
||||
module.exit_json(
|
||||
failed=False,
|
||||
changed=False,
|
||||
msg="Plugin not found or is already uninstalled",
|
||||
command=helm_plugin_list,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
167
plugins/modules/helm_plugin_info.py
Normal file
167
plugins/modules/helm_plugin_info.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: helm_plugin_info
|
||||
short_description: Gather information about Helm plugins
|
||||
version_added: "1.0.0"
|
||||
author:
|
||||
- Abhijeet Kasurde (@Akasurde)
|
||||
requirements:
|
||||
- "helm (https://github.com/helm/helm/releases)"
|
||||
description:
|
||||
- Gather information about Helm plugins installed in namespace.
|
||||
options:
|
||||
release_namespace:
|
||||
description:
|
||||
- Kubernetes namespace where the helm plugins are installed.
|
||||
required: true
|
||||
type: str
|
||||
aliases: [ namespace ]
|
||||
|
||||
#Helm options
|
||||
plugin_name:
|
||||
description:
|
||||
- Name of Helm plugin, to gather particular plugin info.
|
||||
type: str
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.helm_common_options
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Gather Helm plugin info
|
||||
community.kubernetes.helm_plugin_info:
|
||||
|
||||
- name: Gather Helm plugin info
|
||||
community.kubernetes.helm_plugin_info:
|
||||
plugin_name: env
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
stdout:
|
||||
type: str
|
||||
description: Full `helm` command stdout, in case you want to display it or examine the event log
|
||||
returned: always
|
||||
sample: ''
|
||||
stderr:
|
||||
type: str
|
||||
description: Full `helm` command stderr, in case you want to display it or examine the event log
|
||||
returned: always
|
||||
sample: ''
|
||||
command:
|
||||
type: str
|
||||
description: Full `helm` command built by this module, in case you want to re-run the command outside the module or debug a problem.
|
||||
returned: always
|
||||
sample: helm plugin list ...
|
||||
plugin_list:
|
||||
type: list
|
||||
description: Helm plugin dict inside a list
|
||||
returned: always
|
||||
sample: {
|
||||
"name": "env",
|
||||
"version": "0.1.0",
|
||||
"description": "Print out the helm environment."
|
||||
}
|
||||
rc:
|
||||
type: int
|
||||
description: Helm plugin command return code
|
||||
returned: always
|
||||
sample: 1
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
binary_path=dict(type='path'),
|
||||
release_namespace=dict(type='str', required=True, aliases=['namespace']),
|
||||
plugin_name=dict(type='str',),
|
||||
# Helm options
|
||||
context=dict(type='str', aliases=['kube_context'], fallback=(env_fallback, ['K8S_AUTH_CONTEXT'])),
|
||||
kubeconfig=dict(type='path', aliases=['kubeconfig_path'], fallback=(env_fallback, ['K8S_AUTH_KUBECONFIG'])),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
bin_path = module.params.get('binary_path')
|
||||
release_namespace = module.params.get('release_namespace')
|
||||
|
||||
# Helm options
|
||||
kube_context = module.params.get('context')
|
||||
kubeconfig_path = module.params.get('kubeconfig')
|
||||
|
||||
if bin_path is not None:
|
||||
helm_cmd_common = bin_path
|
||||
else:
|
||||
helm_cmd_common = 'helm'
|
||||
|
||||
helm_cmd_common = module.get_bin_path(helm_cmd_common, required=True)
|
||||
|
||||
helm_cmd_common += " plugin"
|
||||
|
||||
if kube_context is not None:
|
||||
helm_cmd_common += " --kube-context " + kube_context
|
||||
|
||||
if kubeconfig_path is not None:
|
||||
helm_cmd_common += " --kubeconfig " + kubeconfig_path
|
||||
|
||||
helm_cmd_common += " --namespace=" + release_namespace
|
||||
|
||||
plugin_name = module.params.get('plugin_name')
|
||||
helm_plugin_list = helm_cmd_common + " list"
|
||||
rc, out, err = module.run_command(helm_plugin_list)
|
||||
if rc != 0 or (out == '' and err == ''):
|
||||
module.fail_json(
|
||||
msg="Failed to get Helm plugin info",
|
||||
command=helm_plugin_list,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
)
|
||||
|
||||
plugin_list = []
|
||||
if out:
|
||||
for line in out.splitlines():
|
||||
if line.startswith("NAME"):
|
||||
continue
|
||||
name, version, description = line.split('\t', 3)
|
||||
name = name.strip()
|
||||
version = version.strip()
|
||||
description = description.strip()
|
||||
if plugin_name is None:
|
||||
plugin_list.append({
|
||||
'name': name,
|
||||
'version': version,
|
||||
'description': description,
|
||||
})
|
||||
continue
|
||||
|
||||
if plugin_name == name:
|
||||
plugin_list.append({
|
||||
'name': name,
|
||||
'version': version,
|
||||
'description': description,
|
||||
})
|
||||
break
|
||||
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
command=helm_plugin_list,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
rc=rc,
|
||||
plugin_list=plugin_list,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,21 +1,20 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: Ansible Project
|
||||
# Copyright: (c) 2020, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: helm_repository
|
||||
|
||||
short_description: Add and remove Helm repository
|
||||
|
||||
version_added: "0.11.0"
|
||||
|
||||
author:
|
||||
- Lucas Boisserie (@LucasBoisserie)
|
||||
|
||||
@@ -24,7 +23,7 @@ requirements:
|
||||
- "yaml (https://pypi.org/project/PyYAML/)"
|
||||
|
||||
description:
|
||||
- Manage Helm repositories
|
||||
- Manage Helm repositories.
|
||||
|
||||
options:
|
||||
binary_path:
|
||||
@@ -60,21 +59,21 @@ options:
|
||||
repo_state:
|
||||
choices: ['present', 'absent']
|
||||
description:
|
||||
- Desirated state of repositoriy.
|
||||
- Desirated state of repository.
|
||||
required: false
|
||||
default: present
|
||||
aliases: [ state ]
|
||||
type: str
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Add default repository
|
||||
helm_repository:
|
||||
community.kubernetes.helm_repository:
|
||||
name: stable
|
||||
repo_url: https://kubernetes-charts.storage.googleapis.com
|
||||
'''
|
||||
|
||||
RETURN = ''' # '''
|
||||
RETURN = r''' # '''
|
||||
|
||||
import traceback
|
||||
|
||||
|
||||
@@ -9,11 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s
|
||||
|
||||
@@ -28,7 +24,7 @@ description:
|
||||
- Pass the object definition from a source file or inline. See examples for reading
|
||||
files and using Jinja templates or vault-encrypted files.
|
||||
- Access to the full range of K8s APIs.
|
||||
- Use the M(k8s_info) module to obtain a list of items about an object of type C(kind)
|
||||
- Use the M(community.kubernetes.k8s_info) module to obtain a list of items about an object of type C(kind)
|
||||
- Authenticate using either a config file, certificates, password or token.
|
||||
- Supports check mode.
|
||||
|
||||
@@ -115,7 +111,7 @@ options:
|
||||
validate:
|
||||
description:
|
||||
- how (if at all) to validate the resource definition against the kubernetes schema.
|
||||
Requires the kubernetes-validate python module
|
||||
Requires the kubernetes-validate python module and openshift >= 0.8.0
|
||||
suboptions:
|
||||
fail_on_error:
|
||||
description: whether to fail on validation errors.
|
||||
@@ -136,12 +132,14 @@ options:
|
||||
- The full definition of an object is needed to generate the hash - this means that deleting an object created with append_hash
|
||||
will only work if the same object is passed with state=absent (alternatively, just use state=absent with the name including
|
||||
the generated hash and append_hash=no)
|
||||
- Requires openshift >= 0.7.2
|
||||
type: bool
|
||||
apply:
|
||||
description:
|
||||
- C(apply) compares the desired resource definition with the previously supplied resource definition,
|
||||
ignoring properties that are automatically generated
|
||||
- C(apply) works better with Services than 'force=yes'
|
||||
- Requires openshift >= 0.9.2
|
||||
- mutually exclusive with C(merge_type)
|
||||
type: bool
|
||||
|
||||
@@ -151,16 +149,16 @@ requirements:
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Create a k8s namespace
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
name: testing
|
||||
api_version: v1
|
||||
kind: Namespace
|
||||
state: present
|
||||
|
||||
- name: Create a Service object from an inline definition
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
definition:
|
||||
apiVersion: v1
|
||||
@@ -182,7 +180,7 @@ EXAMPLES = '''
|
||||
port: 8000
|
||||
|
||||
- name: Remove an existing Service object
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: absent
|
||||
api_version: v1
|
||||
kind: Service
|
||||
@@ -192,31 +190,31 @@ EXAMPLES = '''
|
||||
# Passing the object definition from a file
|
||||
|
||||
- name: Create a Deployment by reading the definition from a local file
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
src: /testing/deployment.yml
|
||||
|
||||
- name: >-
|
||||
Read definition file from the Ansible controller file system.
|
||||
If the definition file has been encrypted with Ansible Vault it will automatically be decrypted.
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('file', '/testing/deployment.yml') | from_yaml }}"
|
||||
|
||||
- name: Read definition file from the Ansible controller file system after Jinja templating
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
|
||||
|
||||
- name: fail on validation errors
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
|
||||
validate:
|
||||
fail_on_error: yes
|
||||
|
||||
- name: warn on validation errors, check for unexpected properties
|
||||
k8s:
|
||||
community.kubernetes.k8s:
|
||||
state: present
|
||||
definition: "{{ lookup('template', '/testing/deployment.yml') | from_yaml }}"
|
||||
validate:
|
||||
@@ -224,7 +222,7 @@ EXAMPLES = '''
|
||||
strict: yes
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- The created, patched, or otherwise present object. Will be empty in the case of a deletion.
|
||||
|
||||
@@ -9,11 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_auth
|
||||
|
||||
@@ -79,7 +75,7 @@ requirements:
|
||||
- requests-oauthlib
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- hosts: localhost
|
||||
module_defaults:
|
||||
group/k8s:
|
||||
@@ -92,7 +88,7 @@ EXAMPLES = '''
|
||||
- include_vars: k8s_passwords.yml
|
||||
|
||||
- name: Log in (obtain access token)
|
||||
k8s_auth:
|
||||
community.kubernetes.k8s_auth:
|
||||
username: admin
|
||||
password: "{{ k8s_admin_password }}"
|
||||
register: k8s_auth_results
|
||||
@@ -100,7 +96,7 @@ EXAMPLES = '''
|
||||
# Previous task provides the token/api_key, while all other parameters
|
||||
# are taken from module_defaults
|
||||
- name: Get a list of all pods from any namespace
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
api_key: "{{ k8s_auth_results.k8s_auth.api_key }}"
|
||||
kind: Pod
|
||||
register: pod_list
|
||||
@@ -108,7 +104,7 @@ EXAMPLES = '''
|
||||
always:
|
||||
- name: If login succeeded, try to log out (revoke access token)
|
||||
when: k8s_auth_results.k8s_auth.api_key is defined
|
||||
k8s_auth:
|
||||
community.kubernetes.k8s_auth:
|
||||
state: absent
|
||||
api_key: "{{ k8s_auth_results.k8s_auth.api_key }}"
|
||||
'''
|
||||
@@ -116,7 +112,7 @@ EXAMPLES = '''
|
||||
# Returned value names need to match k8s modules parameter names, to make it
|
||||
# easy to pass returned values of k8s_auth to other k8s modules.
|
||||
# Discussion: https://github.com/ansible/ansible/pull/50807#discussion_r248827899
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
k8s_auth:
|
||||
description: Kubernetes authentication facts.
|
||||
returned: success
|
||||
@@ -255,7 +251,7 @@ class KubernetesAuthModule(AnsibleModule):
|
||||
|
||||
self.openshift_auth_endpoint = oauth_info['authorization_endpoint']
|
||||
self.openshift_token_endpoint = oauth_info['token_endpoint']
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
self.fail_json(msg="Something went wrong discovering OpenShift OAuth details.",
|
||||
exception=traceback.format_exc())
|
||||
|
||||
@@ -315,7 +311,7 @@ class KubernetesAuthModule(AnsibleModule):
|
||||
"kind": "DeleteOptions"
|
||||
}
|
||||
|
||||
ret = requests.delete(url, headers=headers, json=json, verify=self.con_verify_ca)
|
||||
requests.delete(url, headers=headers, json=json, verify=self.con_verify_ca)
|
||||
# Ignore errors, the token will time out eventually anyway
|
||||
|
||||
def fail(self, msg=None):
|
||||
|
||||
@@ -9,16 +9,14 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_exec
|
||||
|
||||
short_description: Execute command in Pod
|
||||
|
||||
version_added: "0.10.0"
|
||||
|
||||
author: "Tristan de Cacqueray (@tristanC)"
|
||||
|
||||
description:
|
||||
@@ -32,10 +30,13 @@ requirements:
|
||||
- "openshift == 0.4.3"
|
||||
- "PyYAML >= 3.11"
|
||||
|
||||
notes:
|
||||
- Return code C(return_code) for the command executed is added in output in version 1.0.0.
|
||||
options:
|
||||
proxy:
|
||||
description:
|
||||
- The URL of an HTTP proxy to use for the connection. Can also be specified via K8S_AUTH_PROXY environment variable.
|
||||
- The URL of an HTTP proxy to use for the connection.
|
||||
- Can also be specified via I(K8S_AUTH_PROXY) environment variable.
|
||||
- Please note that this module does not pick up typical proxy settings from the environment (e.g. HTTP_PROXY).
|
||||
type: str
|
||||
namespace:
|
||||
@@ -50,7 +51,8 @@ options:
|
||||
required: yes
|
||||
container:
|
||||
description:
|
||||
- The name of the container in the pod to connect to. Defaults to only container if there is only one container in the pod.
|
||||
- The name of the container in the pod to connect to.
|
||||
- Defaults to only container if there is only one container in the pod.
|
||||
type: str
|
||||
required: no
|
||||
command:
|
||||
@@ -60,15 +62,28 @@ options:
|
||||
required: yes
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Execute a command
|
||||
k8s_exec:
|
||||
community.kubernetes.k8s_exec:
|
||||
namespace: myproject
|
||||
pod: zuul-scheduler
|
||||
command: zuul-scheduler full-reconfigure
|
||||
|
||||
- name: Check RC status of command executed
|
||||
community.kubernetes.k8s_exec:
|
||||
namespace: myproject
|
||||
pod: busybox-test
|
||||
command: cmd_with_non_zero_exit_code
|
||||
register: command_status
|
||||
ignore_errors: True
|
||||
|
||||
- name: Check last command status
|
||||
debug:
|
||||
msg: "cmd failed"
|
||||
when: command_status.return_code != 0
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- The command object
|
||||
@@ -87,12 +102,25 @@ result:
|
||||
stderr_lines:
|
||||
description: The command stderr
|
||||
type: str
|
||||
return_code:
|
||||
description: The command status code
|
||||
type: int
|
||||
'''
|
||||
|
||||
import copy
|
||||
import shlex
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import KubernetesAnsibleModule
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
# ImportError are managed by the common module already.
|
||||
pass
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, AUTH_ARG_SPEC
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.client.apis import core_v1_api
|
||||
@@ -102,7 +130,18 @@ except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class KubernetesExecCommand(KubernetesAnsibleModule):
|
||||
class KubernetesExecCommand(K8sAnsibleMixin):
|
||||
|
||||
def __init__(self):
|
||||
module = AnsibleModule(
|
||||
argument_spec=self.argspec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
self.module = module
|
||||
self.params = self.module.params
|
||||
self.fail_json = self.module.fail_json
|
||||
super(KubernetesExecCommand, self).__init__()
|
||||
|
||||
@property
|
||||
def argspec(self):
|
||||
spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
@@ -112,36 +151,54 @@ class KubernetesExecCommand(KubernetesAnsibleModule):
|
||||
spec['command'] = dict(type='str', required=True)
|
||||
return spec
|
||||
|
||||
def execute_module(self):
|
||||
# Load kubernetes.client.Configuration
|
||||
self.get_api_client()
|
||||
api = core_v1_api.CoreV1Api()
|
||||
|
||||
# hack because passing the container as None breaks things
|
||||
optional_kwargs = {}
|
||||
if self.params.get('container'):
|
||||
optional_kwargs['container'] = self.params['container']
|
||||
try:
|
||||
resp = stream(
|
||||
api.connect_get_namespaced_pod_exec,
|
||||
self.params["pod"],
|
||||
self.params["namespace"],
|
||||
command=shlex.split(self.params["command"]),
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
_preload_content=False, **optional_kwargs)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Failed to execute on pod %s"
|
||||
" due to : %s" % (self.params.get('pod'), to_native(e)))
|
||||
stdout, stderr, rc = [], [], 0
|
||||
while resp.is_open():
|
||||
resp.update(timeout=1)
|
||||
if resp.peek_stdout():
|
||||
stdout.append(resp.read_stdout())
|
||||
if resp.peek_stderr():
|
||||
stderr.append(resp.read_stderr())
|
||||
err = resp.read_channel(3)
|
||||
err = yaml.safe_load(err)
|
||||
if err['status'] == 'Success':
|
||||
rc = 0
|
||||
else:
|
||||
rc = int(err['details']['causes'][0]['message'])
|
||||
|
||||
self.module.exit_json(
|
||||
# Some command might change environment, but ultimately failing at end
|
||||
changed=True,
|
||||
stdout="".join(stdout),
|
||||
stderr="".join(stderr),
|
||||
return_code=rc
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
module = KubernetesExecCommand()
|
||||
# Load kubernetes.client.Configuration
|
||||
module.get_api_client()
|
||||
api = core_v1_api.CoreV1Api()
|
||||
|
||||
# hack because passing the container as None breaks things
|
||||
optional_kwargs = {}
|
||||
if module.params.get('container'):
|
||||
optional_kwargs['container'] = module.params['container']
|
||||
resp = stream(
|
||||
api.connect_get_namespaced_pod_exec,
|
||||
module.params["pod"],
|
||||
module.params["namespace"],
|
||||
command=shlex.split(module.params["command"]),
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
_preload_content=False, **optional_kwargs)
|
||||
stdout, stderr = [], []
|
||||
while resp.is_open():
|
||||
resp.update(timeout=1)
|
||||
if resp.peek_stdout():
|
||||
stdout.append(resp.read_stdout())
|
||||
if resp.peek_stderr():
|
||||
stderr.append(resp.read_stderr())
|
||||
module.exit_json(
|
||||
changed=True, stdout="".join(stdout), stderr="".join(stderr))
|
||||
KubernetesExecCommand().execute_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -9,11 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
module: k8s_info
|
||||
|
||||
short_description: Describe Kubernetes (K8s) objects
|
||||
@@ -29,31 +25,15 @@ description:
|
||||
- This module was called C(k8s_facts) before Ansible 2.9. The usage did not change.
|
||||
|
||||
options:
|
||||
api_version:
|
||||
description:
|
||||
- Use to specify the API version. in conjunction with I(kind), I(name), and I(namespace) to identify a
|
||||
specific object.
|
||||
default: v1
|
||||
aliases:
|
||||
- api
|
||||
- version
|
||||
type: str
|
||||
kind:
|
||||
description:
|
||||
- Use to specify an object model. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a
|
||||
specific object.
|
||||
required: yes
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Use to specify an object name. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a
|
||||
specific object.
|
||||
type: str
|
||||
namespace:
|
||||
description:
|
||||
- Use to specify an object namespace. Use in conjunction with I(api_version), I(kind), and I(name)
|
||||
to identify a specific object.
|
||||
- Use to specify an object model.
|
||||
- Use to create, delete, or discover an object without providing a full resource definition.
|
||||
- Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
|
||||
- If I(resource definition) is provided, the I(kind) value from the I(resource_definition)
|
||||
will override this option.
|
||||
type: str
|
||||
required: True
|
||||
label_selectors:
|
||||
description: List of label selectors to use to filter results
|
||||
type: list
|
||||
@@ -65,6 +45,7 @@ options:
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.k8s_auth_options
|
||||
- community.kubernetes.k8s_name_options
|
||||
|
||||
requirements:
|
||||
- "python >= 2.7"
|
||||
@@ -72,9 +53,9 @@ requirements:
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Get an existing Service object
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
api_version: v1
|
||||
kind: Service
|
||||
name: web
|
||||
@@ -82,32 +63,45 @@ EXAMPLES = '''
|
||||
register: web_service
|
||||
|
||||
- name: Get a list of all service objects
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
api_version: v1
|
||||
kind: Service
|
||||
namespace: testing
|
||||
register: service_list
|
||||
|
||||
- name: Get a list of all pods from any namespace
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
kind: Pod
|
||||
register: pod_list
|
||||
|
||||
- name: Search for all Pods labelled app=web
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- app = web
|
||||
- tier in (dev, test)
|
||||
|
||||
- name: Using vars while using label_selectors
|
||||
community.kubernetes.k8s_info:
|
||||
kind: Pod
|
||||
label_selectors:
|
||||
- "app = {{ app_label_web }}"
|
||||
vars:
|
||||
app_label_web: web
|
||||
|
||||
- name: Search for all running pods
|
||||
k8s_info:
|
||||
community.kubernetes.k8s_info:
|
||||
kind: Pod
|
||||
field_selectors:
|
||||
- status.phase=Running
|
||||
|
||||
- name: List custom objects created using CRD
|
||||
community.kubernetes.k8s_info:
|
||||
kind: MyCustomObject
|
||||
api_version: "stable.example.com/v1"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
resources:
|
||||
description:
|
||||
- The object(s) that exists
|
||||
@@ -136,19 +130,25 @@ resources:
|
||||
type: dict
|
||||
'''
|
||||
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import KubernetesAnsibleModule, AUTH_ARG_SPEC
|
||||
import copy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, AUTH_ARG_SPEC)
|
||||
|
||||
class KubernetesInfoModule(KubernetesAnsibleModule):
|
||||
|
||||
class KubernetesInfoModule(K8sAnsibleMixin):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
KubernetesAnsibleModule.__init__(self, *args,
|
||||
supports_check_mode=True,
|
||||
**kwargs)
|
||||
if self._name == 'k8s_facts':
|
||||
self.deprecate("The 'k8s_facts' module has been renamed to 'k8s_info'", version='2.13')
|
||||
module = AnsibleModule(
|
||||
argument_spec=self.argspec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
self.module = module
|
||||
self.params = self.module.params
|
||||
self.fail_json = self.module.fail_json
|
||||
self.exit_json = self.module.exit_json
|
||||
super(KubernetesInfoModule, self).__init__()
|
||||
|
||||
def execute_module(self):
|
||||
self.client = self.get_api_client()
|
||||
|
||||
@@ -9,15 +9,13 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
module: k8s_log
|
||||
|
||||
short_description: Fetch logs from Kubernetes resources
|
||||
|
||||
version_added: "0.10.0"
|
||||
|
||||
author:
|
||||
- "Fabian von Feilitzsch (@fabianvf)"
|
||||
|
||||
@@ -28,40 +26,25 @@ description:
|
||||
- Analogous to `kubectl logs` or `oc logs`
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.k8s_auth_options
|
||||
- community.kubernetes.k8s_name_options
|
||||
options:
|
||||
api_version:
|
||||
description:
|
||||
- Use to specify the API version. in conjunction with I(kind), I(name), and I(namespace) to identify a
|
||||
specific object.
|
||||
- If using I(label_selector), cannot be overridden
|
||||
default: v1
|
||||
aliases:
|
||||
- api
|
||||
- version
|
||||
type: str
|
||||
kind:
|
||||
description:
|
||||
- Use to specify an object model. Use in conjunction with I(api_version), I(name), and I(namespace) to identify a
|
||||
specific object.
|
||||
- If using I(label_selector), cannot be overridden
|
||||
required: no
|
||||
- Use to specify an object model.
|
||||
- Use in conjunction with I(api_version), I(name), and I(namespace) to identify a specific object.
|
||||
- If using I(label_selectors), cannot be overridden.
|
||||
type: str
|
||||
default: Pod
|
||||
type: str
|
||||
namespace:
|
||||
description:
|
||||
- Use to specify an object namespace. Use in conjunction with I(api_version), I(kind), and I(name)
|
||||
to identify a specfic object.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Use to specify an object name. Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a
|
||||
specific object.
|
||||
- Only one of I(name) or I(label_selector) may be provided
|
||||
- Use to specify an object name.
|
||||
- Use in conjunction with I(api_version), I(kind) and I(namespace) to identify a specific object.
|
||||
- Only one of I(name) or I(label_selectors) may be provided.
|
||||
type: str
|
||||
label_selectors:
|
||||
description:
|
||||
- List of label selectors to use to filter results
|
||||
- Only one of I(name) or I(label_selector) may be provided
|
||||
- Only one of I(name) or I(label_selectors) may be provided.
|
||||
type: list
|
||||
elements: str
|
||||
container:
|
||||
@@ -78,16 +61,16 @@ requirements:
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Get a log from a Pod
|
||||
k8s_log:
|
||||
community.kubernetes.k8s_log:
|
||||
name: example-1
|
||||
namespace: testing
|
||||
register: log
|
||||
|
||||
# This will get the log from the first Pod found matching the selector
|
||||
- name: Log a Pod matching a label selector
|
||||
k8s_log:
|
||||
community.kubernetes.k8s_log:
|
||||
namespace: testing
|
||||
label_selectors:
|
||||
- app=example
|
||||
@@ -95,7 +78,7 @@ EXAMPLES = '''
|
||||
|
||||
# This will get the log from a single Pod managed by this Deployment
|
||||
- name: Get a log from a Deployment
|
||||
k8s_log:
|
||||
community.kubernetes.k8s_log:
|
||||
api_version: apps/v1
|
||||
kind: Deployment
|
||||
namespace: testing
|
||||
@@ -104,7 +87,7 @@ EXAMPLES = '''
|
||||
|
||||
# This will get the log from a single Pod managed by this DeploymentConfig
|
||||
- name: Get a log from a DeploymentConfig
|
||||
k8s_log:
|
||||
community.kubernetes.k8s_log:
|
||||
api_version: apps.openshift.io/v1
|
||||
kind: DeploymentConfig
|
||||
namespace: testing
|
||||
@@ -112,7 +95,7 @@ EXAMPLES = '''
|
||||
register: log
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
log:
|
||||
type: str
|
||||
description:
|
||||
@@ -128,28 +111,34 @@ log_lines:
|
||||
|
||||
import copy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import PY2
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import KubernetesAnsibleModule
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin, AUTH_ARG_SPEC, NAME_ARG_SPEC)
|
||||
|
||||
|
||||
class KubernetesLogModule(KubernetesAnsibleModule):
|
||||
class KubernetesLogModule(K8sAnsibleMixin):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
KubernetesAnsibleModule.__init__(self, *args,
|
||||
supports_check_mode=True,
|
||||
**kwargs)
|
||||
def __init__(self):
|
||||
module = AnsibleModule(
|
||||
argument_spec=self.argspec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
self.module = module
|
||||
self.params = self.module.params
|
||||
self.fail_json = self.module.fail_json
|
||||
self.fail = self.module.fail_json
|
||||
self.exit_json = self.module.exit_json
|
||||
super(KubernetesLogModule, self).__init__()
|
||||
|
||||
@property
|
||||
def argspec(self):
|
||||
args = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
args.update(NAME_ARG_SPEC)
|
||||
args.update(
|
||||
dict(
|
||||
kind=dict(default='Pod'),
|
||||
api_version=dict(default='v1', aliases=['api', 'version']),
|
||||
name=dict(),
|
||||
namespace=dict(),
|
||||
kind=dict(type='str', default='Pod'),
|
||||
container=dict(),
|
||||
label_selectors=dict(type='list', elements='str', default=[]),
|
||||
)
|
||||
@@ -158,6 +147,7 @@ class KubernetesLogModule(KubernetesAnsibleModule):
|
||||
|
||||
def execute_module(self):
|
||||
name = self.params.get('name')
|
||||
namespace = self.params.get('namespace')
|
||||
label_selector = ','.join(self.params.get('label_selectors', {}))
|
||||
if name and label_selector:
|
||||
self.fail(msg='Only one of name or label_selectors can be provided')
|
||||
@@ -167,16 +157,16 @@ class KubernetesLogModule(KubernetesAnsibleModule):
|
||||
v1_pods = self.find_resource('Pod', 'v1', fail=True)
|
||||
|
||||
if 'log' not in resource.subresources:
|
||||
if not self.params.get('name'):
|
||||
if not name:
|
||||
self.fail(msg='name must be provided for resources that do not support the log subresource')
|
||||
instance = resource.get(name=self.params['name'], namespace=self.params.get('namespace'))
|
||||
instance = resource.get(name=name, namespace=namespace)
|
||||
label_selector = ','.join(self.extract_selectors(instance))
|
||||
resource = v1_pods
|
||||
|
||||
if label_selector:
|
||||
instances = v1_pods.get(namespace=self.params['namespace'], label_selector=label_selector)
|
||||
instances = v1_pods.get(namespace=namespace, label_selector=label_selector)
|
||||
if not instances.items:
|
||||
self.fail(msg='No pods in namespace {0} matched selector {1}'.format(self.params['namespace'], label_selector))
|
||||
self.fail(msg='No pods in namespace {0} matched selector {1}'.format(namespace, label_selector))
|
||||
# This matches the behavior of kubectl when logging pods via a selector
|
||||
name = instances.items[0].metadata.name
|
||||
resource = v1_pods
|
||||
@@ -187,7 +177,7 @@ class KubernetesLogModule(KubernetesAnsibleModule):
|
||||
|
||||
log = serialize_log(resource.log.get(
|
||||
name=name,
|
||||
namespace=self.params.get('namespace'),
|
||||
namespace=namespace,
|
||||
serialize=False,
|
||||
**kwargs
|
||||
))
|
||||
|
||||
@@ -9,11 +9,8 @@ from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_scale
|
||||
|
||||
@@ -39,9 +36,9 @@ requirements:
|
||||
- "PyYAML >= 3.11"
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Scale deployment up, and extend timeout
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
api_version: v1
|
||||
kind: Deployment
|
||||
name: elastic
|
||||
@@ -50,7 +47,7 @@ EXAMPLES = '''
|
||||
wait_timeout: 60
|
||||
|
||||
- name: Scale deployment down when current replicas match
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
api_version: v1
|
||||
kind: Deployment
|
||||
name: elastic
|
||||
@@ -59,7 +56,7 @@ EXAMPLES = '''
|
||||
replicas: 2
|
||||
|
||||
- name: Increase job parallelism
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
api_version: batch/v1
|
||||
kind: job
|
||||
name: pi-with-timeout
|
||||
@@ -69,25 +66,25 @@ EXAMPLES = '''
|
||||
# Match object using local file or inline definition
|
||||
|
||||
- name: Scale deployment based on a file from the local filesystem
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
src: /myproject/elastic_deployment.yml
|
||||
replicas: 3
|
||||
wait: no
|
||||
|
||||
- name: Scale deployment based on a template output
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
resource_definition: "{{ lookup('template', '/myproject/elastic_deployment.yml') | from_yaml }}"
|
||||
replicas: 3
|
||||
wait: no
|
||||
|
||||
- name: Scale deployment based on a file from the Ansible controller filesystem
|
||||
k8s_scale:
|
||||
community.kubernetes.k8s_scale:
|
||||
resource_definition: "{{ lookup('file', '/myproject/elastic_deployment.yml') | from_yaml }}"
|
||||
replicas: 3
|
||||
wait: no
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- If a change was made, will return the patched object, otherwise returns the existing object.
|
||||
@@ -114,6 +111,11 @@ result:
|
||||
description: Current status details for the object.
|
||||
returned: success
|
||||
type: complex
|
||||
duration:
|
||||
description: elapsed time of task in seconds
|
||||
returned: when C(wait) is true
|
||||
type: int
|
||||
sample: 48
|
||||
'''
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.scale import KubernetesAnsibleScaleModule
|
||||
|
||||
@@ -9,11 +9,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
module: k8s_service
|
||||
|
||||
@@ -26,43 +22,10 @@ description:
|
||||
|
||||
extends_documentation_fragment:
|
||||
- community.kubernetes.k8s_auth_options
|
||||
- community.kubernetes.k8s_resource_options
|
||||
- community.kubernetes.k8s_state_options
|
||||
|
||||
options:
|
||||
resource_definition:
|
||||
description:
|
||||
- A partial YAML definition of the Service object being created/updated. Here you can define Kubernetes
|
||||
Service Resource parameters not covered by this module's parameters.
|
||||
- "NOTE: I(resource_definition) has lower priority than module parameters. If you try to define e.g.
|
||||
I(metadata.namespace) here, that value will be ignored and I(metadata) used instead."
|
||||
aliases:
|
||||
- definition
|
||||
- inline
|
||||
type: dict
|
||||
src:
|
||||
description:
|
||||
- "Provide a path to a file containing a valid YAML definition of an object dated. Mutually
|
||||
exclusive with I(resource_definition). NOTE: I(kind), I(api_version), I(resource_name), and I(namespace)
|
||||
will be overwritten by corresponding values found in the configuration read in from the I(src) file."
|
||||
- Reads from the local file system. To read from the Ansible controller's file system, use the file lookup
|
||||
plugin or template lookup plugin, combined with the from_yaml filter, and pass the result to
|
||||
I(resource_definition). See Examples below.
|
||||
type: path
|
||||
state:
|
||||
description:
|
||||
- Determines if an object should be created, patched, or deleted. When set to C(present), an object will be
|
||||
created, if it does not already exist. If set to C(absent), an existing object will be deleted. If set to
|
||||
C(present), an existing object will be patched, if its attributes differ from those specified using
|
||||
module options and I(resource_definition).
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
type: str
|
||||
force:
|
||||
description:
|
||||
- If set to C(True), and I(state) is C(present), an existing object will be replaced.
|
||||
default: false
|
||||
type: bool
|
||||
merge_type:
|
||||
description:
|
||||
- Whether to override the default patch merge approach with a specific type. By default, the strategic
|
||||
@@ -125,9 +88,9 @@ requirements:
|
||||
- openshift >= 0.6.2
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
EXAMPLES = r'''
|
||||
- name: Expose https port with ClusterIP
|
||||
k8s_service:
|
||||
community.kubernetes.k8s_service:
|
||||
state: present
|
||||
name: test-https
|
||||
namespace: default
|
||||
@@ -138,7 +101,7 @@ EXAMPLES = '''
|
||||
key: special
|
||||
|
||||
- name: Expose https port with ClusterIP using spec
|
||||
k8s_service:
|
||||
community.kubernetes.k8s_service:
|
||||
state: present
|
||||
name: test-https
|
||||
namespace: default
|
||||
@@ -151,7 +114,7 @@ EXAMPLES = '''
|
||||
key: special
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
RETURN = r'''
|
||||
result:
|
||||
description:
|
||||
- The created, patched, or otherwise present Service object. Will be empty in the case of a deletion.
|
||||
@@ -185,7 +148,7 @@ import traceback
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.common import AUTH_ARG_SPEC, COMMON_ARG_SPEC, RESOURCE_ARG_SPEC
|
||||
from ansible_collections.community.kubernetes.plugins.module_utils.raw import KubernetesRawModule
|
||||
|
||||
|
||||
@@ -194,25 +157,10 @@ SERVICE_ARG_SPEC = {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
},
|
||||
'state': {
|
||||
'default': 'present',
|
||||
'choices': ['present', 'absent'],
|
||||
},
|
||||
'force': {
|
||||
'type': 'bool',
|
||||
'default': False,
|
||||
},
|
||||
'resource_definition': {
|
||||
'type': 'dict',
|
||||
'aliases': ['definition', 'inline']
|
||||
},
|
||||
'name': {'required': True},
|
||||
'namespace': {'required': True},
|
||||
'merge_type': {'type': 'list', 'elements': 'str', 'choices': ['json', 'merge', 'strategic-merge']},
|
||||
'selector': {'type': 'dict'},
|
||||
'src': {
|
||||
'type': 'path',
|
||||
},
|
||||
'type': {
|
||||
'type': 'str',
|
||||
'choices': [
|
||||
@@ -244,6 +192,8 @@ class KubernetesService(KubernetesRawModule):
|
||||
def argspec(self):
|
||||
""" argspec property builder """
|
||||
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
||||
argument_spec.update(COMMON_ARG_SPEC)
|
||||
argument_spec.update(RESOURCE_ARG_SPEC)
|
||||
argument_spec.update(SERVICE_ARG_SPEC)
|
||||
return argument_spec
|
||||
|
||||
|
||||
Reference in New Issue
Block a user