73
73
description:
74
74
- The remote absolute path or data set where the content should be copied to.
75
75
- C(dest) can be a USS file, directory or MVS data set name.
76
- - If C(src) and C(dest) are files and if the parent directory of C(dest)
77
- does not exist, then the task will fail
76
+ - If C(dest) has missing parent directories, they will be created.
78
77
- If C(dest) is a nonexistent USS file, it will be created.
79
78
- If C(dest) is a nonexistent data set, it will be created following the
80
79
process outlined here and in the C(volume) option.
664
663
from ansible_collections .ibm .ibm_zos_core .plugins .module_utils .ansible_module import (
665
664
AnsibleModuleHelper ,
666
665
)
667
- from ansible .module_utils ._text import to_bytes
666
+ from ansible .module_utils ._text import to_bytes , to_native
668
667
from ansible .module_utils .basic import AnsibleModule
669
668
from ansible .module_utils .six import PY3
670
669
from re import IGNORECASE
678
677
679
678
if PY3 :
680
679
from re import fullmatch
680
+ import pathlib
681
681
else :
682
682
from re import match as fullmatch
683
683
@@ -975,6 +975,25 @@ def copy_to_uss(
975
975
src , dest , src_ds_type , src_member , member_name = member_name
976
976
)
977
977
else :
978
+ norm_dest = os .path .normpath (dest )
979
+ dest_parent_dir , tail = os .path .split (norm_dest )
980
+ if PY3 :
981
+ path_helper = pathlib .Path (dest_parent_dir )
982
+ if dest_parent_dir != "/" and not path_helper .exists ():
983
+ path_helper .mkdir (parents = True , exist_ok = True )
984
+ else :
985
+ # When using Python 2, instead of using pathlib, we'll use os.makedirs.
986
+ # pathlib is not available in Python 2.
987
+ try :
988
+ if dest_parent_dir != "/" and not os .path .exists (dest_parent_dir ):
989
+ os .makedirs (dest_parent_dir )
990
+ except os .error as err :
991
+ # os.makedirs throws an error whether the directories were already
992
+ # present or their creation failed. There's no exist_ok to tell it
993
+ # to ignore the first case, so we ignore it manually.
994
+ if "File exists" not in err :
995
+ raise CopyOperationError (msg = to_native (err ))
996
+
978
997
if os .path .isfile (temp_path or conv_path or src ):
979
998
dest = self ._copy_to_file (src , dest , conv_path , temp_path )
980
999
changed_files = None
@@ -1084,7 +1103,7 @@ def _copy_to_dir(
1084
1103
# Restoring permissions for preexisting files and subdirectories.
1085
1104
for filepath , permissions in original_permissions :
1086
1105
mode = "0{0:o}" .format (stat .S_IMODE (permissions ))
1087
- self .module .set_mode_if_different (os .path .join (dest_dir , filepath ), mode , False )
1106
+ self .module .set_mode_if_different (os .path .join (dest , filepath ), mode , False )
1088
1107
except Exception as err :
1089
1108
raise CopyOperationError (
1090
1109
msg = "Error while copying data to destination directory {0}" .format (dest_dir ),
@@ -1110,21 +1129,23 @@ def _get_changed_files(self, src, dest, copy_directory):
1110
1129
for the files and directories already present on the
1111
1130
destination.
1112
1131
"""
1113
- original_files = self ._walk_uss_tree (dest ) if os .path .exists (dest ) else []
1114
1132
copied_files = self ._walk_uss_tree (src )
1115
1133
1116
1134
# It's not needed to normalize the path because it was already normalized
1117
1135
# on _copy_to_dir.
1118
1136
parent_dir = os .path .basename (src ) if copy_directory else ''
1119
1137
1120
- changed_files = [
1121
- relative_path for relative_path in copied_files
1122
- if os .path .join (parent_dir , relative_path ) not in original_files
1123
- ]
1138
+ changed_files = []
1139
+ original_files = []
1140
+ for relative_path in copied_files :
1141
+ if os .path .exists (os .path .join (dest , parent_dir , relative_path )):
1142
+ original_files .append (relative_path )
1143
+ else :
1144
+ changed_files .append (relative_path )
1124
1145
1125
1146
# Creating tuples with (filename, permissions).
1126
1147
original_permissions = [
1127
- (filepath , os .stat (os .path .join (dest , filepath )).st_mode )
1148
+ (filepath , os .stat (os .path .join (dest , parent_dir , filepath )).st_mode )
1128
1149
for filepath in original_files
1129
1150
]
1130
1151
@@ -2030,6 +2051,9 @@ def run_module(module, arg_def):
2030
2051
# If temp_path, the plugin has copied a file from the controller to USS.
2031
2052
if temp_path or "/" in src :
2032
2053
src_ds_type = "USS"
2054
+
2055
+ if remote_src and os .path .isdir (src ):
2056
+ is_src_dir = True
2033
2057
else :
2034
2058
if data_set .DataSet .data_set_exists (src_name ):
2035
2059
if src_member and not data_set .DataSet .data_set_member_exists (src ):
@@ -2055,7 +2079,17 @@ def run_module(module, arg_def):
2055
2079
2056
2080
if is_uss :
2057
2081
dest_ds_type = "USS"
2058
- dest_exists = os .path .exists (dest )
2082
+ if src_ds_type == "USS" and not is_src_dir and (dest .endswith ("/" ) or os .path .isdir (dest )):
2083
+ src_basename = os .path .basename (src ) if src else "inline_copy"
2084
+ dest = os .path .normpath ("{0}/{1}" .format (dest , src_basename ))
2085
+
2086
+ if dest .startswith ("//" ):
2087
+ dest = dest .replace ("//" , "/" )
2088
+
2089
+ if is_src_dir and not src .endswith ("/" ):
2090
+ dest_exists = os .path .exists (os .path .normpath ("{0}/{1}" .format (dest , os .path .basename (src ))))
2091
+ else :
2092
+ dest_exists = os .path .exists (dest )
2059
2093
2060
2094
if dest_exists and not os .access (dest , os .W_OK ):
2061
2095
module .fail_json (msg = "Destination {0} is not writable" .format (dest ))
@@ -2173,14 +2207,23 @@ def run_module(module, arg_def):
2173
2207
2174
2208
# Creating an emergency backup or an empty data set to use as a model to
2175
2209
# be able to restore the destination in case the copy fails.
2210
+ emergency_backup = ""
2176
2211
if dest_exists and not force :
2177
2212
if is_uss or not data_set .DataSet .is_empty (dest_name ):
2178
2213
use_backup = True
2179
2214
if is_uss :
2215
+ # When copying a directory without a trailing slash,
2216
+ # appending the source's base name to the backup path to
2217
+ # avoid backing up the whole parent directory that won't
2218
+ # be modified.
2219
+ src_basename = os .path .basename (src ) if src else ''
2220
+ backup_dest = "{0}/{1}" .format (dest , src_basename ) if is_src_dir and not src .endswith ("/" ) else dest
2221
+ backup_dest = os .path .normpath (backup_dest )
2180
2222
emergency_backup = tempfile .mkdtemp ()
2181
- emergency_backup = backup_data (dest , dest_ds_type , emergency_backup , tmphlq )
2223
+ emergency_backup = backup_data (backup_dest , dest_ds_type , emergency_backup , tmphlq )
2182
2224
else :
2183
- emergency_backup = backup_data (dest , dest_ds_type , None , tmphlq )
2225
+ if not (dest_ds_type in data_set .DataSet .MVS_PARTITIONED and src_member and not dest_member_exists ):
2226
+ emergency_backup = backup_data (dest , dest_ds_type , None , tmphlq )
2184
2227
# If dest is an empty data set, instead create a data set to
2185
2228
# use as a model when restoring.
2186
2229
else :
0 commit comments