From d3e9ef3c35447b27986869a25ab90dbdf7f0d54a Mon Sep 17 00:00:00 2001
From: Anthony Bourguignon <contact+github@toniob.net>
Date: Mon, 6 Jan 2020 17:56:40 +0100
Subject: [PATCH] add a path_join filter which joins path components (#62713)

---
 changelogs/fragments/62713-add-path_join-filter.yaml |  2 ++
 docs/docsite/rst/user_guide/playbooks_filters.rst    |  6 ++++++
 lib/ansible/plugins/filter/core.py                   | 12 ++++++++++++
 test/integration/targets/filters/files/foo.txt       |  4 ++++
 test/integration/targets/filters/templates/foo.j2    |  4 ++++
 5 files changed, 28 insertions(+)
 create mode 100644 changelogs/fragments/62713-add-path_join-filter.yaml

diff --git a/changelogs/fragments/62713-add-path_join-filter.yaml b/changelogs/fragments/62713-add-path_join-filter.yaml
new file mode 100644
index 00000000000..9b5bf0d8431
--- /dev/null
+++ b/changelogs/fragments/62713-add-path_join-filter.yaml
@@ -0,0 +1,2 @@
+minor_changes:
+    - core filters - Adding ``path_join`` filter to the core filters list
diff --git a/docs/docsite/rst/user_guide/playbooks_filters.rst b/docs/docsite/rst/user_guide/playbooks_filters.rst
index 553c3a87ef6..48e96f7a06c 100644
--- a/docs/docsite/rst/user_guide/playbooks_filters.rst
+++ b/docs/docsite/rst/user_guide/playbooks_filters.rst
@@ -1308,6 +1308,12 @@ To get the root and extension of a path or filename (new in version 2.0)::
     # with path == 'nginx.conf' the return would be ('nginx', '.conf')
     {{ path | splitext }}
 
+To join one or more path components::
+
+    {{ ('/etc', path, 'subdir', file) | path_join }}
+
+.. versionadded:: 2.10
+
 String filters
 ==============
 
diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py
index eecf7eafe80..f83194be4f4 100644
--- a/lib/ansible/plugins/filter/core.py
+++ b/lib/ansible/plugins/filter/core.py
@@ -581,6 +581,17 @@ def random_mac(value, seed=None):
     return value + re.sub(r'(..)', r':\1', rnd)
 
 
+def path_join(paths):
+    ''' takes a sequence or a string, and return a concatenation
+        of the different members '''
+    if isinstance(paths, string_types):
+        return os.path.join(paths)
+    elif is_sequence(paths):
+        return os.path.join(*paths)
+    else:
+        raise AnsibleFilterError("|path_join expects string or sequence, got %s instead." % type(paths))
+
+
 class FilterModule(object):
     ''' Ansible core jinja2 filters '''
 
@@ -612,6 +623,7 @@ class FilterModule(object):
             'dirname': partial(unicode_wrap, os.path.dirname),
             'expanduser': partial(unicode_wrap, os.path.expanduser),
             'expandvars': partial(unicode_wrap, os.path.expandvars),
+            'path_join': path_join,
             'realpath': partial(unicode_wrap, os.path.realpath),
             'relpath': partial(unicode_wrap, os.path.relpath),
             'splitext': partial(unicode_wrap, os.path.splitext),
diff --git a/test/integration/targets/filters/files/foo.txt b/test/integration/targets/filters/files/foo.txt
index 203a1be2b22..a5839cee958 100644
--- a/test/integration/targets/filters/files/foo.txt
+++ b/test/integration/targets/filters/files/foo.txt
@@ -52,6 +52,10 @@ files to exist and are passthrus to the python os.path functions
 /etc/motd with basename = motd
 /etc/motd with dirname  = /etc
 
+path_join_simple = /etc/subdir/test
+path_join_with_slash = /test
+path_join_relative = etc/subdir/test
+
 TODO: realpath follows symlinks.  There isn't a test for this just now.
 
 TODO: add tests for set theory operations like union
diff --git a/test/integration/targets/filters/templates/foo.j2 b/test/integration/targets/filters/templates/foo.j2
index 5661723e5ba..55893cc1321 100644
--- a/test/integration/targets/filters/templates/foo.j2
+++ b/test/integration/targets/filters/templates/foo.j2
@@ -46,6 +46,10 @@ files to exist and are passthrus to the python os.path functions
 /etc/motd with basename = {{ '/etc/motd' | basename }}
 /etc/motd with dirname  = {{ '/etc/motd' | dirname }}
 
+path_join_simple = {{ ('/etc', 'subdir', 'test') | path_join }}
+path_join_with_slash = {{ ('/etc', 'subdir', '/test') | path_join }}
+path_join_relative = {{ ('etc', 'subdir', 'test') | path_join }}
+
 TODO: realpath follows symlinks.  There isn't a test for this just now.
 
 TODO: add tests for set theory operations like union