azure_move.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. #!/usr/bin/env python
  2. from azure_storage.methods import copy_blob, create_parent_parser, delete_container, delete_file, delete_folder, \
  3. extract_common_path, move_prep, setup_arguments
  4. from argparse import ArgumentParser, RawTextHelpFormatter
  5. import coloredlogs
  6. import logging
  7. import sys
  8. import os
  9. class AzureContainerMove(object):
  10. def main(self):
  11. # Validate the container names, and prepare all the necessary clients
  12. self.container_name, self.target_container, self.blob_service_client, self.source_container_client, \
  13. self.target_container_client = move_prep(
  14. passphrase=self.passphrase,
  15. account_name=self.account_name,
  16. container_name=self.container_name,
  17. target_container=self.target_container)
  18. # Rename (move) the container
  19. self.move_container(source_container_client=self.source_container_client,
  20. blob_service_client=self.blob_service_client,
  21. container_name=self.container_name,
  22. target_container=self.target_container,
  23. path=self.path,
  24. storage_tier=self.storage_tier)
  25. # Delete the original container once the copy is complete
  26. delete_container(blob_service_client=self.blob_service_client,
  27. container_name=self.container_name,
  28. account_name=self.account_name)
  29. @staticmethod
  30. def move_container(source_container_client, blob_service_client, container_name, target_container, path,
  31. storage_tier):
  32. """
  33. Rename (move) the specified container in Azure storage
  34. :param source_container_client: type azure.storage.blob.BlobServiceClient.ContainerClient for source container
  35. :param blob_service_client: type: azure.storage.blob.BlobServiceClient
  36. :param container_name: type str: Name of the container in which the folder is located
  37. :param target_container: type str: Name of the container into which the folder is to be moved
  38. :param path: type str: Path of folders in which the files are to be placed
  39. :param storage_tier: type str: Storage tier to use for the container
  40. """
  41. # Create a generator containing all the blobs in the container
  42. generator = source_container_client.list_blobs()
  43. for blob_file in generator:
  44. # Copy the file to the new container
  45. copy_blob(blob_file=blob_file,
  46. blob_service_client=blob_service_client,
  47. container_name=container_name,
  48. target_container=target_container,
  49. path=path,
  50. storage_tier=storage_tier,
  51. category='container')
  52. def __init__(self, container_name, account_name, passphrase, target_container, path, storage_tier):
  53. # Set the container name variable
  54. self.container_name = container_name
  55. # Initialise necessary class variables
  56. self.passphrase = passphrase
  57. self.account_name = account_name
  58. self.target_container = target_container
  59. self.path = path
  60. self.storage_tier = storage_tier
  61. self.connect_str = str()
  62. self.blob_service_client = None
  63. self.source_container_client = None
  64. self.target_container_client = None
  65. class AzureMove(object):
  66. def main(self):
  67. # Validate the container names, and prepare all the necessary clients
  68. self.container_name, self.target_container, self.blob_service_client, self.source_container_client, \
  69. self.target_container_client = move_prep(
  70. passphrase=self.passphrase,
  71. account_name=self.account_name,
  72. container_name=self.container_name,
  73. target_container=self.target_container)
  74. # Run the proper method depending on whether a file or a folder is requested
  75. if self.category == 'file':
  76. self.move_file(source_container_client=self.source_container_client,
  77. object_name=self.object_name,
  78. blob_service_client=self.blob_service_client,
  79. container_name=self.container_name,
  80. target_container=self.target_container,
  81. path=self.path,
  82. storage_tier=self.storage_tier)
  83. delete_file(container_client=self.source_container_client,
  84. object_name=self.object_name,
  85. blob_service_client=self.blob_service_client,
  86. container_name=self.container_name)
  87. elif self.category == 'folder':
  88. self.move_folder(source_container_client=self.source_container_client,
  89. object_name=self.object_name,
  90. blob_service_client=self.blob_service_client,
  91. container_name=self.container_name,
  92. target_container=self.target_container,
  93. path=self.path,
  94. category=self.category,
  95. storage_tier=self.storage_tier)
  96. delete_folder(container_client=self.source_container_client,
  97. object_name=self.object_name,
  98. blob_service_client=self.blob_service_client,
  99. container_name=self.container_name,
  100. account_name=self.account_name)
  101. else:
  102. logging.error(f'Something is wrong. There is no {self.category} option available')
  103. raise SystemExit
  104. @staticmethod
  105. def move_file(source_container_client, object_name, blob_service_client, container_name, target_container,
  106. path, storage_tier):
  107. """
  108. Move the specified file to the desired container in Azure storage
  109. :param source_container_client: type azure.storage.blob.BlobServiceClient.ContainerClient for source container
  110. :param object_name: type str: Name and path of folder to move in Azure storage
  111. :param blob_service_client: type: azure.storage.blob.BlobServiceClient
  112. :param container_name: type str: Name of the container in which the folder is located
  113. :param target_container: type str: Name of the container into which the folder is to be moved
  114. :param path: type str: Path of folders in which the files are to be placed
  115. :param storage_tier: type str: Storage tier to use for the file
  116. """
  117. # Create a generator containing all the blobs in the container
  118. generator = source_container_client.list_blobs()
  119. # Create a boolean to determine if the blob has been located
  120. present = False
  121. for blob_file in generator:
  122. # Filter for the blob name
  123. if blob_file.name == object_name:
  124. # Update the blob presence variable
  125. present = True
  126. # Copy the file to the new container
  127. copy_blob(blob_file=blob_file,
  128. blob_service_client=blob_service_client,
  129. container_name=container_name,
  130. target_container=target_container,
  131. path=path,
  132. storage_tier=storage_tier)
  133. # Send a warning to the user that the blob could not be found
  134. if not present:
  135. logging.error(f'Could not locate the desired file {object_name}')
  136. raise SystemExit
  137. @staticmethod
  138. def move_folder(source_container_client, object_name, blob_service_client, container_name, target_container, path,
  139. storage_tier, category):
  140. """
  141. Move the specified folder (and its contents) to the desired container in Azure storage
  142. :param source_container_client: type azure.storage.blob.BlobServiceClient.ContainerClient for source container
  143. :param object_name: type str: Name and path of folder to move in Azure storage
  144. :param blob_service_client: type: azure.storage.blob.BlobServiceClient
  145. :param container_name: type str: Name of the container in which the folder is located
  146. :param target_container: type str: Name of the container into which the folder is to be moved
  147. :param path: type str: Path of folders in which the files are to be placed
  148. :param storage_tier: type str: Storage tier to use for the moved folder
  149. :param category: type str: Category of object to be copied. Limited to file or folder
  150. """
  151. # Create a generator containing all the blobs in the container
  152. generator = source_container_client.list_blobs()
  153. # Create a boolean to determine if the blob has been located
  154. present = False
  155. for blob_file in generator:
  156. # Extract the common path between the current file and the requested folder
  157. common_path = extract_common_path(object_name=object_name,
  158. blob_file=blob_file)
  159. # Only copy the file if there is a common path between the object path and the blob path (they match)
  160. if common_path is not None:
  161. # Update the blob presence variable
  162. present = True
  163. # Copy the file to the new container
  164. copy_blob(blob_file=blob_file,
  165. blob_service_client=blob_service_client,
  166. container_name=container_name,
  167. target_container=target_container,
  168. path=path,
  169. object_name=object_name,
  170. category=category,
  171. common_path=common_path,
  172. storage_tier=storage_tier)
  173. # Send a warning to the user that the blob could not be found
  174. if not present:
  175. logging.error(f'Could not locate the desired folder {object_name}')
  176. raise SystemExit
  177. def __init__(self, object_name, container_name, account_name, passphrase, target_container, path,
  178. storage_tier, category):
  179. self.object_name = object_name
  180. # Set the container name variable
  181. self.container_name = container_name
  182. # Initialise necessary class variables
  183. self.passphrase = passphrase
  184. self.account_name = account_name
  185. self.target_container = target_container
  186. self.path = path
  187. self.storage_tier = storage_tier
  188. self.category = category
  189. self.connect_str = str()
  190. self.blob_service_client = None
  191. self.source_container_client = None
  192. self.target_container_client = None
  193. def container_move(args):
  194. """
  195. Run the AzureContainerMove method
  196. :param args: type ArgumentParser arguments
  197. """
  198. logging.info(f'Renaming container {args.container_name} to {args.target_container} in Azure storage '
  199. f'account {args.account_name}')
  200. move_container = AzureContainerMove(container_name=args.container_name,
  201. account_name=args.account_name,
  202. passphrase=args.passphrase,
  203. target_container=args.target_container,
  204. path=args.reset_path,
  205. storage_tier=args.storage_tier)
  206. move_container.main()
  207. def file_move(args):
  208. """
  209. Run the AzureMove method for a file
  210. :param args: type ArgumentParser arguments
  211. """
  212. logging.info(f'Moving file {args.file} from {args.container_name} to {args.target_container} in Azure storage '
  213. f'account {args.account_name}')
  214. move_file = AzureMove(object_name=args.file,
  215. container_name=args.container_name,
  216. account_name=args.account_name,
  217. passphrase=args.passphrase,
  218. target_container=args.target_container,
  219. path=args.reset_path,
  220. storage_tier=args.storage_tier,
  221. category='file')
  222. move_file.main()
  223. def folder_move(args):
  224. """
  225. Run the AzureMove method for a folder
  226. :param args: type ArgumentParser arguments
  227. """
  228. logging.info(f'Moving folder {args.folder} from {args.container_name} to {args.target_container} in Azure storage '
  229. f'account {args.account_name}')
  230. move_folder = AzureMove(object_name=args.folder,
  231. container_name=args.container_name,
  232. account_name=args.account_name,
  233. passphrase=args.passphrase,
  234. target_container=args.target_container,
  235. path=args.reset_path,
  236. storage_tier=args.storage_tier,
  237. category='folder')
  238. move_folder.main()
  239. def cli():
  240. parser = ArgumentParser(description='Move containers, files, or folders in Azure storage')
  241. # Create the parental parser, and the subparser
  242. subparsers, parent_parser = create_parent_parser(parser=parser)
  243. parent_parser.add_argument('-t', '--target_container',
  244. required=True,
  245. help='The target container to which the container/file/folder is to be moved '
  246. '(this can be the same as the container_name if you want to move a file/folder'
  247. ' within a container')
  248. parent_parser.add_argument('-r', '--reset_path',
  249. type=str,
  250. help='Set the path of the container/file/folder within a folder in the target container '
  251. 'e.g. sequence_data/220202-m05722. If you want to place it directly in the '
  252. 'container without any nesting, use or \'\'')
  253. parent_parser.add_argument('-s', '--storage_tier',
  254. type=str,
  255. default='Hot',
  256. choices=['Hot', 'Cool', 'Archive'],
  257. metavar='STORAGE_TIER',
  258. help='Set the storage tier for the container/file/folder to be moved. '
  259. 'Options are "Hot", "Cool", and "Archive". Default is Hot')
  260. # Container move subparser
  261. container_move_subparser = subparsers.add_parser(parents=[parent_parser],
  262. name='container',
  263. description='Move a container in Azure storage',
  264. formatter_class=RawTextHelpFormatter,
  265. help='Move a container in Azure storage')
  266. container_move_subparser.set_defaults(func=container_move)
  267. # File move subparser
  268. file_move_subparser = subparsers.add_parser(parents=[parent_parser],
  269. name='file',
  270. description='Move a file within Azure storage',
  271. formatter_class=RawTextHelpFormatter,
  272. help='Move a file within Azure storage')
  273. file_move_subparser.add_argument('-f', '--file',
  274. type=str,
  275. required=True,
  276. help='Name of blob file to move in Azure storage. '
  277. 'e.g. 2022-SEQ-0001_S1_L001_R1_001.fastq.gz')
  278. file_move_subparser.set_defaults(func=file_move)
  279. # Folder move subparser
  280. folder_move_subparser = subparsers.add_parser(parents=[parent_parser],
  281. name='folder',
  282. description='Move a folder within Azure storage',
  283. formatter_class=RawTextHelpFormatter,
  284. help='Move a folder within Azure storage')
  285. folder_move_subparser.add_argument('-f', '--folder',
  286. type=str,
  287. required=True,
  288. help='Name of folder to move in Azure storage. '
  289. 'e.g. InterOp')
  290. folder_move_subparser.set_defaults(func=folder_move)
  291. # Set up the arguments, and run the appropriate subparser
  292. arguments = setup_arguments(parser=parser)
  293. # Return to the requested logging level, as it has been increased to WARNING to suppress the log being filled with
  294. # information from azure.core.pipeline.policies.http_logging_policy
  295. coloredlogs.install(level=arguments.verbosity.upper())
  296. logging.info('Move complete')
  297. # Prevent the arguments being printed to the console (they are returned in order for the tests to work)
  298. sys.stderr = open(os.devnull, 'w')
  299. return arguments
  300. if __name__ == '__main__':
  301. cli()