Repository: ansible-collections/community.general

Issue:
Elastic client is not properly closed
### Summary

When running anything with `urllib3>=1.26.16` and `community.general.plugins.callback.elastic`, Elastic client is not closed properly.

Related to https://github.com/elastic/apm-agent-python/issues/1912


### Issue Type

Bug Report

### Component Name

plugins.callback.elastic

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.15.6]
  config file = /home/iurisilvio/code/oss/apm-agent-python-1912/ansible.cfg
  configured module search path = ['/home/iurisilvio/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/iurisilvio/.pyenv/versions/3.10.6/envs/apm-agent-python-1912/lib/python3.10/site-packages/ansible
  ansible collection location = /home/iurisilvio/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/iurisilvio/.pyenv/versions/apm-agent-python-1912/bin/ansible
  python version = 3.10.6 (main, Aug 21 2022, 09:43:53) [GCC 11.2.0] (/home/iurisilvio/.pyenv/versions/3.10.6/envs/apm-agent-python-1912/bin/python)
  jinja version = 3.1.2
  libyaml = True

```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general

# /home/iurisilvio/.pyenv/versions/3.10.6/envs/apm-agent-python-1912/lib/python3.10/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 7.5.1  
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CALLBACKS_ENABLED(/home/iurisilvio/code/oss/apm-agent-python-1912/ansible.cfg) = ['community.general.elastic']
CONFIG_FILE() = /home/iurisilvio/code/oss/apm-agent-python-1912/ansible.cfg
```


### OS / Environment

_No response_

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)

```


### Expected Results

Elastic resources closed to not show warnings.

### Actual Results

```console (paste below)

```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/callback/elastic.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/callback/elastic.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @v1v
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/callback/elastic.py b/plugins/callback/elastic.py
--- a/plugins/callback/elastic.py
+++ b/plugins/callback/elastic.py
@@ -84,6 +84,7 @@
 import uuid
 
 from collections import OrderedDict
+from contextlib import closing
 from os.path import basename
 
 from ansible.errors import AnsibleError, AnsibleRuntimeError
@@ -201,24 +202,25 @@ def generate_distributed_traces(self, tasks_data, status, end_time, traceparent,
 
         apm_cli = self.init_apm_client(apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key)
         if apm_cli:
-            instrument()  # Only call this once, as early as possible.
-            if traceparent:
-                parent = trace_parent_from_string(traceparent)
-                apm_cli.begin_transaction("Session", trace_parent=parent, start=parent_start_time)
-            else:
-                apm_cli.begin_transaction("Session", start=parent_start_time)
-            # Populate trace metadata attributes
-            if self.ansible_version is not None:
-                label(ansible_version=self.ansible_version)
-            label(ansible_session=self.session, ansible_host_name=self.host, ansible_host_user=self.user)
-            if self.ip_address is not None:
-                label(ansible_host_ip=self.ip_address)
-
-            for task_data in tasks:
-                for host_uuid, host_data in task_data.host_data.items():
-                    self.create_span_data(apm_cli, task_data, host_data)
-
-            apm_cli.end_transaction(name=__name__, result=status, duration=end_time - parent_start_time)
+            with closing(apm_cli):
+                instrument()  # Only call this once, as early as possible.
+                if traceparent:
+                    parent = trace_parent_from_string(traceparent)
+                    apm_cli.begin_transaction("Session", trace_parent=parent, start=parent_start_time)
+                else:
+                    apm_cli.begin_transaction("Session", start=parent_start_time)
+                # Populate trace metadata attributes
+                if self.ansible_version is not None:
+                    label(ansible_version=self.ansible_version)
+                label(ansible_session=self.session, ansible_host_name=self.host, ansible_host_user=self.user)
+                if self.ip_address is not None:
+                    label(ansible_host_ip=self.ip_address)
+
+                for task_data in tasks:
+                    for host_uuid, host_data in task_data.host_data.items():
+                        self.create_span_data(apm_cli, task_data, host_data)
+
+                apm_cli.end_transaction(name=__name__, result=status, duration=end_time - parent_start_time)
 
     def create_span_data(self, apm_cli, task_data, host_data):
         """ create the span with the given TaskData and HostData """


Test patch: