ec50b5a7 |
## Amazon S3 manager
## Author: Michal Ludvig <michal@logix.cz>
## http://www.logix.cz/michal
## License: GPL Version 2 |
afd51b6c |
## Copyright: TGRMN Software and contributors |
ec50b5a7 |
|
b6e1cada |
import logging |
f06cf98f |
from logging import debug, info, warning, error |
b6e1cada |
import re |
a495865c |
import os |
dc590d62 |
import sys |
2d7ceec9 |
import Progress |
fa664913 |
from SortedDict import SortedDict |
dc590d62 |
import httplib |
a122d976 |
try:
import json
except ImportError, e:
pass |
b6e1cada |
|
b008e471 |
class Config(object): |
d439efb4 |
_instance = None
_parsed_files = []
_doc = {}
access_key = ""
secret_key = "" |
dc590d62 |
access_token = "" |
d439efb4 |
host_base = "s3.amazonaws.com"
host_bucket = "%(bucket)s.s3.amazonaws.com"
simpledb_host = "sdb.amazonaws.com"
cloudfront_host = "cloudfront.amazonaws.com"
verbosity = logging.WARNING
progress_meter = True
progress_class = Progress.ProgressCR
send_chunk = 4096
recv_chunk = 4096
list_md5 = False
human_readable_sizes = False
extra_headers = SortedDict(ignore_case = True)
force = False |
754f575d |
server_side_encryption = False |
d439efb4 |
enable = None
get_continue = False |
dc071cc1 |
put_continue = False
upload_id = None |
d439efb4 |
skip_existing = False
recursive = False |
40deabb4 |
restore_days = 1 |
d439efb4 |
acl_public = None
acl_grants = []
acl_revokes = []
proxy_host = ""
proxy_port = 3128
encrypt = False
dry_run = False |
833f07bb |
add_encoding_exts = "" |
d439efb4 |
preserve_attrs = True
preserve_attrs_list = [
'uname', # Verbose owner Name (e.g. 'root')
'uid', # Numeric user ID (e.g. 0)
'gname', # Group name (e.g. 'users')
'gid', # Numeric group ID (e.g. 100)
'atime', # Last access timestamp
'mtime', # Modification timestamp
'ctime', # Creation timestamp
'mode', # File mode (e.g. rwxr-xr-x = 755) |
1703df70 |
'md5', # File MD5 (if known) |
d439efb4 |
#'acl', # Full ACL (not yet supported)
]
delete_removed = False |
255d96b8 |
delete_after = False |
552df705 |
delete_after_fetch = False |
f230f799 |
max_delete = -1 |
d439efb4 |
_doc['delete_removed'] = "[sync] Remove remote S3 objects when local file has been deleted" |
c3deb6a8 |
delay_updates = False |
d439efb4 |
gpg_passphrase = ""
gpg_command = ""
gpg_encrypt = "%(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s"
gpg_decrypt = "%(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s"
use_https = False
bucket_location = "US"
default_mime_type = "binary/octet-stream" |
0d477b9c |
guess_mime_type = True |
b4207d9c |
use_mime_magic = True |
35612e61 |
mime_type = "" |
880e0dec |
enable_multipart = True |
80310166 |
multipart_chunk_size_mb = 15 # MB |
d439efb4 |
# List of checks to be performed for 'sync'
sync_checks = ['size', 'md5'] # 'weak-timestamp'
# List of compiled REGEXPs
exclude = []
include = []
# Dict mapping compiled REGEXPs back to their textual form
debug_exclude = {}
debug_include = {}
encoding = "utf-8"
urlencoding_mode = "normal"
log_target_prefix = ""
reduced_redundancy = False
follow_symlinks = False
socket_timeout = 300
invalidate_on_cf = False |
c0a81434 |
# joseprio: new flags for default index invalidation
invalidate_default_index_on_cf = False
invalidate_default_index_root_on_cf = True |
d439efb4 |
website_index = "index.html"
website_error = ""
website_endpoint = "http://%(bucket)s.s3-website-%(location)s.amazonaws.com/" |
07c9e2de |
additional_destinations = [] |
3ce5e989 |
files_from = [] |
488c9565 |
cache_file = "" |
50a42333 |
add_headers = "" |
91464838 |
ignore_failed_copy = False |
d439efb4 |
## Creating a singleton
def __new__(self, configfile = None):
if self._instance is None:
self._instance = object.__new__(self)
return self._instance
def __init__(self, configfile = None):
if configfile: |
dc590d62 |
try:
self.read_config_file(configfile)
except IOError, e:
if 'AWS_CREDENTIAL_FILE' in os.environ:
self.env_config()
if len(self.access_key)==0: |
98692c12 |
self.role_config() |
dc590d62 |
def role_config(self): |
a122d976 |
if sys.version_info[0] * 10 + sys.version_info[1] < 26:
error("IAM authentication requires Python 2.6 or newer")
raise
if not 'json' in sys.modules:
error("IAM authentication not available -- missing module json")
raise |
dc590d62 |
try: |
a122d976 |
conn = httplib.HTTPConnection(host='169.254.169.254', timeout = 2) |
dc590d62 |
conn.request('GET', "/latest/meta-data/iam/security-credentials/")
resp = conn.getresponse()
files = resp.read() |
98692c12 |
if resp.status == 200 and len(files)>1: |
dc590d62 |
conn.request('GET', "/latest/meta-data/iam/security-credentials/%s"%files)
resp=conn.getresponse()
if resp.status == 200:
creds=json.load(resp)
Config().update_option('access_key', creds['AccessKeyId'].encode('ascii'))
Config().update_option('secret_key', creds['SecretAccessKey'].encode('ascii'))
Config().update_option('access_token', creds['Token'].encode('ascii'))
else:
raise IOError
else:
raise IOError
except:
raise
def role_refresh(self):
try:
self.role_config()
except:
warning("Could not refresh role")
def env_config(self):
cred_content = ""
try:
cred_file = open(os.environ['AWS_CREDENTIAL_FILE'],'r')
cred_content = cred_file.read()
except IOError, e:
debug("Error %d accessing credentials file %s" % (e.errno,os.environ['AWS_CREDENTIAL_FILE']))
r_data = re.compile("^\s*(?P<orig_key>\w+)\s*=\s*(?P<value>.*)")
r_quotes = re.compile("^\"(.*)\"\s*$")
if len(cred_content)>0:
for line in cred_content.splitlines():
is_data = r_data.match(line)
is_data = r_data.match(line)
if is_data:
data = is_data.groupdict()
if r_quotes.match(data["value"]):
data["value"] = data["value"][1:-1]
if data["orig_key"]=="AWSAccessKeyId":
data["key"] = "access_key"
elif data["orig_key"]=="AWSSecretKey":
data["key"] = "secret_key"
else: |
98692c12 |
del data["key"] |
dc590d62 |
if "key" in data:
Config().update_option(data["key"], data["value"])
if data["key"] in ("access_key", "secret_key", "gpg_passphrase"): |
2c41f7e1 |
print_value = ("%s...%d_chars...%s") % (data["value"][:2], len(data["value"]) - 3, data["value"][-1:]) |
dc590d62 |
else:
print_value = data["value"]
debug("env_Config: %s->%s" % (data["key"], print_value)) |
d439efb4 |
def option_list(self):
retval = []
for option in dir(self):
## Skip attributes that start with underscore or are not string, int or bool
option_type = type(getattr(Config, option))
if option.startswith("_") or \
not (option_type in (
type("string"), # str
type(42), # int
type(True))): # bool
continue
retval.append(option)
return retval
def read_config_file(self, configfile):
cp = ConfigParser(configfile)
for option in self.option_list():
self.update_option(option, cp.get(option)) |
50a42333 |
if cp.get('add_headers'):
for option in cp.get('add_headers').split(","):
(key, value) = option.split(':')
self.extra_headers[key.replace('_', '-').strip()] = value.strip()
|
d439efb4 |
self._parsed_files.append(configfile)
def dump_config(self, stream):
ConfigDumper(stream).dump("default", self)
def update_option(self, option, value):
if value is None:
return |
7d1ea1d7 |
|
a495865c |
#### Handle environment reference
if str(value).startswith("$"):
return self.update_option(option, os.getenv(str(value)[1:])) |
7d1ea1d7 |
|
d439efb4 |
#### Special treatment of some options
## verbosity must be known to "logging" module
if option == "verbosity": |
7d1ea1d7 |
# support integer verboisities |
d439efb4 |
try: |
7d1ea1d7 |
value = int(value)
except ValueError, e:
try:
# otherwise it must be a key known to the logging module
value = logging._levelNames[value]
except KeyError:
error("Config: verbosity level '%s' is not valid" % value)
return
|
d439efb4 |
## allow yes/no, true/false, on/off and 1/0 for boolean options
elif type(getattr(Config, option)) is type(True): # bool
if str(value).lower() in ("true", "yes", "on", "1"): |
7d1ea1d7 |
value = True |
d439efb4 |
elif str(value).lower() in ("false", "no", "off", "0"): |
7d1ea1d7 |
value = False |
d439efb4 |
else:
error("Config: value of option '%s' must be Yes or No, not '%s'" % (option, value)) |
7d1ea1d7 |
return
|
d439efb4 |
elif type(getattr(Config, option)) is type(42): # int
try: |
7d1ea1d7 |
value = int(value) |
d439efb4 |
except ValueError, e:
error("Config: value of option '%s' must be an integer, not '%s'" % (option, value)) |
7d1ea1d7 |
return
setattr(Config, option, value) |
b008e471 |
|
5a736f08 |
class ConfigParser(object): |
d439efb4 |
def __init__(self, file, sections = []):
self.cfg = {}
self.parse_file(file, sections)
def parse_file(self, file, sections = []):
debug("ConfigParser: Reading file '%s'" % file)
if type(sections) != type([]):
sections = [sections]
in_our_section = True
f = open(file, "r")
r_comment = re.compile("^\s*#.*")
r_empty = re.compile("^\s*$")
r_section = re.compile("^\[([^\]]+)\]")
r_data = re.compile("^\s*(?P<key>\w+)\s*=\s*(?P<value>.*)")
r_quotes = re.compile("^\"(.*)\"\s*$")
for line in f:
if r_comment.match(line) or r_empty.match(line):
continue
is_section = r_section.match(line)
if is_section:
section = is_section.groups()[0]
in_our_section = (section in sections) or (len(sections) == 0)
continue
is_data = r_data.match(line)
if is_data and in_our_section:
data = is_data.groupdict()
if r_quotes.match(data["value"]):
data["value"] = data["value"][1:-1]
self.__setitem__(data["key"], data["value"])
if data["key"] in ("access_key", "secret_key", "gpg_passphrase"): |
2c41f7e1 |
print_value = ("%s...%d_chars...%s") % (data["value"][:2], len(data["value"]) - 3, data["value"][-1:]) |
d439efb4 |
else:
print_value = data["value"]
debug("ConfigParser: %s->%s" % (data["key"], print_value))
continue
warning("Ignoring invalid line in '%s': %s" % (file, line))
def __getitem__(self, name):
return self.cfg[name]
def __setitem__(self, name, value):
self.cfg[name] = value
def get(self, name, default = None):
if self.cfg.has_key(name):
return self.cfg[name]
return default |
5a736f08 |
class ConfigDumper(object): |
d439efb4 |
def __init__(self, stream):
self.stream = stream |
5a736f08 |
|
d439efb4 |
def dump(self, section, config):
self.stream.write("[%s]\n" % section)
for option in config.option_list(): |
7d1ea1d7 |
value = getattr(config, option)
if option == "verbosity":
# we turn level numbers back into strings if possible
if isinstance(value,int) and value in logging._levelNames:
value = logging._levelNames[value]
self.stream.write("%s = %s\n" % (option, value)) |
5a736f08 |
|
344cadc8 |
# vim:et:ts=4:sts=4:ai |