├── setup.py ├── .gitignore ├── README.md └── removeVault.py /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import json 3 | setup( 4 | name = "GlacierVaultRemove", 5 | version = "0.1", 6 | packages = find_packages(), 7 | 8 | # Project uses reStructuredText, so ensure that the docutils get 9 | # installed or upgraded on the target machine 10 | install_requires = ['boto>=2.9.9'], 11 | 12 | # metadata for upload to PyPI 13 | author = "Leeroy Brun", 14 | author_email = "leeroy.brun@gmail.com", 15 | description = "Tool used to remove all archives stored inside an Amazon Glacier vault.", 16 | license = "MIT", 17 | keywords = "aws amazon glacier boto archives vaults", 18 | url = "https://github.com/leeroybrun/glacier-vault-remove", 19 | ) 20 | 21 | with open("credentials.json", "w") as outfile: 22 | json.dump({'AWSAccessKeyId': '', 'AWSSecretKey': ''}, outfile, indent=4) 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ENV 2 | credentials.json 3 | *.sublime* 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | 59 | # Sphinx documentation 60 | docs/_build/ 61 | 62 | # PyBuilder 63 | target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | glacier-vault-remove 2 | ====================== 3 | 4 | This tool can help you remove an Amazon Glacier vault, even if it's not empty. 5 | 6 | It will remove all archives contained inside the vault, and then remove the vault itself. 7 | 8 | It is intended to work well with vaults containing a lot of archives. I developed it because my vault contained more than 500'000 archives, and all other softwares crashed when I tried to remove all of them. 9 | 10 | ## Install 11 | 12 | You can install the dependencies (boto) by calling the install script : 13 | 14 | ```shell 15 | python setup.py install 16 | ``` 17 | 18 | ## Configure 19 | 20 | Then create a `credentials.json` file in the same directory as `removeVault.py` : 21 | 22 | ```json 23 | { 24 | "AWSAccessKeyId": "YOURACCESSKEY", 25 | "AWSSecretKey": "YOURSECRETKEY" 26 | } 27 | ``` 28 | 29 | ## Use 30 | 31 | You can then use the script like this : 32 | 33 | ```shell 34 | python .\removeVault.py [|LIST] [DEBUG] 35 | ``` 36 | 37 | Example : 38 | 39 | ```shell 40 | python .\removeVault.py eu-west-1 my_vault 41 | ``` 42 | 43 | ## List 44 | 45 | If you don't know the vault name, you can generate a list like this: 46 | 47 | ```shell 48 | python .\removeVault.py eu-west-1 LIST 49 | ``` 50 | 51 | ## Debug 52 | 53 | By default, only the INFO messages will be printed to console. You can add a third argument "DEBUG" to the removeVault.py script if you want to show all messages. 54 | 55 | Example : 56 | 57 | ```shell 58 | python .\removeVault.py eu-west-1 my_vault DEBUG 59 | ``` 60 | 61 | Licence 62 | ====================== 63 | (The MIT License) 64 | 65 | Copyright (C) 2013 Leeroy Brun, www.leeroy.me 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 68 | 69 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 70 | 71 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 72 | 73 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/leeroybrun/glacier-vault-remove/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 74 | -------------------------------------------------------------------------------- /removeVault.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # -*- coding: UTF-8 -*- 4 | 5 | import sys 6 | import json 7 | import time 8 | import os.path 9 | import logging 10 | import boto.glacier 11 | from socket import gethostbyname, gaierror 12 | 13 | def printException(): 14 | exc_type, exc_value = sys.exc_info()[:2] 15 | logging.error('Exception "%s" occured with message "%s"', exc_type.__name__, exc_value) 16 | 17 | # Default logging config 18 | logging.basicConfig(format='%(asctime)s - %(levelname)s : %(message)s', level=logging.INFO, datefmt='%H:%M:%S') 19 | 20 | # Get arguments 21 | if len(sys.argv) >= 3: 22 | regionName = sys.argv[1] 23 | vaultName = sys.argv[2] 24 | else: 25 | # If there are missing arguments, display usage example and exit 26 | logging.error('Usage: %s [|LIST] [DEBUG]', sys.argv[0]) 27 | sys.exit(1) 28 | 29 | # Get custom logging level 30 | if len(sys.argv) == 4 and sys.argv[3] == 'DEBUG': 31 | logging.info('Logging level set to DEBUG.') 32 | logging.basicConfig(level=logging.DEBUG) 33 | 34 | # Load credentials 35 | try: 36 | f = open('credentials.json', 'r') 37 | config = json.loads(f.read()) 38 | f.close() 39 | except: 40 | logging.error('Cannot load "credentials.json" file...') 41 | printException() 42 | sys.exit(1) 43 | 44 | try: 45 | logging.info('Connecting to Amazon Glacier...') 46 | glacier = boto.glacier.connect_to_region(regionName, aws_access_key_id=config['AWSAccessKeyId'], aws_secret_access_key=config['AWSSecretKey']) 47 | except: 48 | printException() 49 | sys.exit(1) 50 | 51 | if vaultName == 'LIST': 52 | try: 53 | logging.info('Getting list of vaults...') 54 | vaults = glacier.list_vaults() 55 | except: 56 | printException() 57 | sys.exit(1) 58 | 59 | for vault in vaults: 60 | logging.info(vault.name) 61 | 62 | exit(0) 63 | 64 | try: 65 | logging.info('Getting selected vault...') 66 | vault = glacier.get_vault(vaultName) 67 | except: 68 | printException() 69 | sys.exit(1) 70 | 71 | logging.info('Getting jobs list...') 72 | jobList = vault.list_jobs() 73 | jobID = '' 74 | 75 | # Check if a job already exists 76 | for job in jobList: 77 | if job.action == 'InventoryRetrieval': 78 | logging.info('Found existing inventory retrieval job...') 79 | jobID = job.id 80 | 81 | if jobID == '': 82 | logging.info('No existing job found, initiate inventory retrieval...') 83 | try: 84 | jobID = vault.retrieve_inventory(description='Python Amazon Glacier Removal Tool') 85 | except: 86 | printException() 87 | sys.exit(1) 88 | 89 | logging.debug('Job ID : %s', jobID) 90 | 91 | # Get job status 92 | job = vault.get_job(jobID) 93 | 94 | while job.status_code == 'InProgress': 95 | logging.info('Inventory not ready, sleep for 30 mins...') 96 | 97 | time.sleep(60*30) 98 | 99 | job = vault.get_job(jobID) 100 | 101 | if job.status_code == 'Succeeded': 102 | logging.info('Inventory retrieved, parsing data...') 103 | inventory = json.loads(job.get_output().read().decode('utf-8')) 104 | 105 | logging.info('Removing archives... please be patient, this may take some time...'); 106 | for archive in inventory['ArchiveList']: 107 | if archive['ArchiveId'] != '': 108 | logging.debug('Remove archive ID : %s', archive['ArchiveId']) 109 | try: 110 | vault.delete_archive(archive['ArchiveId']) 111 | except: 112 | printException() 113 | 114 | logging.info('Sleep 2 mins before retrying...') 115 | time.sleep(60*2) 116 | 117 | logging.info('Retry to remove archive ID : %s', archive['ArchiveId']) 118 | try: 119 | vault.delete_archive(archive['ArchiveId']) 120 | logging.info('Successfully removed archive ID : %s', archive['ArchiveId']) 121 | except: 122 | logging.error('Cannot remove archive ID : %s', archive['ArchiveId']) 123 | 124 | logging.info('Removing vault...') 125 | try: 126 | vault.delete() 127 | logging.info('Vault removed.') 128 | except: 129 | printException() 130 | logging.error('We can’t remove the vault now. Please wait some time and try again. You can also remove it from the AWS console, now that all archives have been removed.') 131 | 132 | else: 133 | logging.info('Vault retrieval failed.') 134 | --------------------------------------------------------------------------------