website.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import sys
  2. import os, os.path
  3. import logging
  4. import readline
  5. import getpass
  6. from boto.s3.key import Key
  7. import credentials
  8. import config
  9. import s3_util
  10. import util
  11. from prompt_util import get_yes_no, get_input, clear_screen
  12. logger = logging.getLogger("vaporfile.website")
  13. def prompt_create_website(args):
  14. #This is the information we'll gather:
  15. user_config = None
  16. bucket_name = None
  17. using_own_domain = None
  18. sync_path = None
  19. directory_index = None
  20. error_index = None
  21. use_existing_bucket = False
  22. clear_screen()
  23. print(" Amazon S3 Website Creation Wizard ".center(80,"+"))
  24. print("This will guide you through the process of creating a new website on Amazon S3.")
  25. print("No changes to your S3 account will occur until you accept changes at the end.")
  26. print("You may press Ctrl-C at any point to quit without saving.")
  27. print("".center(80,"+"))
  28. print("")
  29. user_config = config.get_config()
  30. try:
  31. user_config["credentials"]
  32. except KeyError:
  33. print("No existing account information found.")
  34. if get_yes_no("Would you like to setup your Amazon AWS account? [Y/n] : ",default=True):
  35. credentials.prompt_save_credentials({})
  36. else:
  37. print("")
  38. print("Cannot proceed without Amazon account information.")
  39. sys.exit(1)
  40. user_config = config.load_config()
  41. conn = s3_util.get_connection()
  42. using_own_domain = get_yes_no("Will you be using your own domain name? [y/n] : ")
  43. if using_own_domain:
  44. print("")
  45. print("Amazon S3 websites hosted using your own domain require a CNAME configured")
  46. print("with your DNS service. Unfortunately, this means that you cannot use your root")
  47. print("domain with S3, you must use a subdomain.")
  48. print("")
  49. while True:
  50. bucket_name = get_input("What is the the fully qualified subdomain you would like to use?\n[eg. www.yourdomain.com] : ",accept_blank=False)
  51. print("Checking to see if {0} is available...".format(bucket_name))
  52. if not s3_util.test_bucket_exists(conn, bucket_name):
  53. break
  54. print("")
  55. #The bucket already exists.. by the user?
  56. if bucket_name in s3_util.get_bucket_names(conn):
  57. print("It looks like you've already configured an S3 bucket "
  58. "with that name.\n")
  59. print("WARNING: If you proceed, existing files in this "
  60. "bucket may be lost!")
  61. if get_yes_no("Are you sure you want to use {0}? [y/n]"\
  62. .format(bucket_name)):
  63. use_existing_bucket = True
  64. break
  65. print("")
  66. else:
  67. print("Sorry, it looks like someone else owns that bucket name. Contact Amazon support")
  68. print("for help if you own the domain you chose. This is an unfortunate side-effect of")
  69. print("Amazon choosing a globally flat namespace for buckets.")
  70. print("")
  71. else:
  72. print("")
  73. print("Without using your own domain, you will need to create a unique bucket name.")
  74. print("You will only be able to access your website through the bucket address")
  75. print("Amazon provides, which is a bit long and cumbersome.")
  76. print("Example: yourwebsite.s3-website-us-east-1.amazonaws.com")
  77. print("")
  78. while True:
  79. bucket_name = get_input("What is the bucket name you would like to use?\n[eg. yourwebsite] : ")
  80. print("Checking to see if {0} is available...".format(bucket_name))
  81. if not s3_util.test_bucket_exists(conn, bucket_name):
  82. break
  83. print("")
  84. print("Sorry, that bucketname is already used. Try another.")
  85. clear_screen()
  86. print(" Local Path Selection ".center(80,"+"))
  87. print("This tool works by synchronizing a local directory to your S3 bucket.")
  88. print("Each time you synchronize, this tool will upload new or changed files")
  89. print("as well as delete any files no longer found in the local directory.")
  90. print("".center(80,"+"))
  91. print("")
  92. while True:
  93. sync_path = get_input("What is the full local directory path you want "
  94. "synchronized?\n[eg. /home/{0}/website ] : ".\
  95. format(getpass.getuser()), accept_blank=False)
  96. if os.path.isdir(sync_path):
  97. break
  98. elif os.path.isfile(sync_path):
  99. print("Sorry, that's not a directory. Please try again.")
  100. else:
  101. if get_yes_no("This directory does not exist. Would you like to create it? (y/n)"):
  102. try:
  103. util.mkdir(sync_path)
  104. except OSError:
  105. print("Permission denied. Try again.")
  106. else:
  107. break
  108. print("")
  109. clear_screen()
  110. print(" Configuration Options ".center(80,"+"))
  111. print("")
  112. if get_yes_no("Would you like to use index.html as your default index file? (Y/n)", default=True):
  113. directory_index = "index.html"
  114. else:
  115. while True:
  116. print("What file would you like to serve when directories are requested?")
  117. directory_index = get_input("[eg. index.html] : ")
  118. if directory_index != "":
  119. break
  120. print("You must enter a directory index. Most users should choose index.html")
  121. print("")
  122. if get_yes_no("Would you like a special 404 handler when files aren't found? (y/N) : ", default=False):
  123. while True:
  124. print("What file would you like to serve when files aren't found?")
  125. error_index = get_input("[eg. 404.html] : ")
  126. if error_index != "":
  127. break
  128. print("")
  129. clear_screen()
  130. print(" Confirmation ".center(80,"+"))
  131. print("OK, we've gathered all the necessary information about your new website.")
  132. print("Let's review:")
  133. print("")
  134. if using_own_domain:
  135. print(" Your domain name:".ljust(35)+bucket_name)
  136. print(" Amazon S3 bucket name:".ljust(35)+bucket_name)
  137. print(" Local path to synchronize:".ljust(35)+sync_path)
  138. print(" Index file:".ljust(35)+directory_index)
  139. if error_index:
  140. print(" Error index file:".ljust(35)+error_index)
  141. print("")
  142. if get_yes_no("Would you like to save this configuration now? [y/n] : "):
  143. website = S3Website(
  144. bucket_name, sync_path, index=directory_index,
  145. error_doc=error_index)
  146. user_config["websites"][bucket_name] = website.to_config()
  147. website.create(use_existing_bucket=use_existing_bucket)
  148. if not use_existing_bucket:
  149. print("Amazon S3 bucket created!")
  150. config.save_config(user_config)
  151. print("Website configuration saved!")
  152. website_endpoint = website.get_bucket().get_website_endpoint()
  153. s3_endpoint = website_endpoint.replace(bucket_name+".","")
  154. print("Your Amazon website endpoint: {0}".format(website_endpoint))
  155. if using_own_domain:
  156. print("Your DNS service needs a CNAME record pointing {0} "
  157. "to {1}".format(bucket_name, s3_endpoint))
  158. print("")
  159. print("To upload your website run this command:")
  160. print("")
  161. print(" vaporfile -v upload {0}".format(bucket_name))
  162. print("")
  163. def upload_website(args):
  164. user_config = config.get_config()
  165. try:
  166. website = S3Website.from_config(user_config["websites"][args.WEBSITE])
  167. except KeyError:
  168. print("")
  169. print("Can't find a website configuration called {0}".format(args.WEBSITE))
  170. print("Maybe you need to create it first? Run: vaporfile create")
  171. return
  172. try:
  173. credentials.check_credentials(user_config)
  174. except credentials.VaporfileCredentialException:
  175. print("")
  176. print("Can't find credentials. You need to run: vaporfile "
  177. "credentials store")
  178. print("")
  179. return
  180. website.synchronize(delete=not args.no_delete)
  181. def remove_website(args):
  182. user_config = config.get_config()
  183. try:
  184. site = user_config["websites"][args.WEBSITE]
  185. except KeyError:
  186. print("")
  187. print("Unknown website configuration : {0}".format(args.WEBSITE))
  188. print("")
  189. else:
  190. del user_config["websites"][args.WEBSITE]
  191. config.save_config(user_config)
  192. print("")
  193. print("Local website configuration removed : {0}".format(args.WEBSITE))
  194. print("")
  195. def list_websites(args):
  196. try:
  197. user_config = config.load_config()
  198. except IOError:
  199. print("")
  200. print("Can't find a configuration. You need to run: vaporfile create")
  201. print("")
  202. return
  203. if len(user_config["websites"]) == 0:
  204. print("")
  205. print("No websites have been created yet. You need to run: vaporfile create")
  206. print("")
  207. return
  208. for name, website in user_config["websites"].items():
  209. print((" "+name).ljust(35)+"- "+website["localpath"])
  210. class S3Website(object):
  211. """Tool for maintaining a static website in S3"""
  212. def __init__(self, bucketname, localpath, index="index.html",
  213. error_doc="404.html",**kwargs):
  214. self.bucketname = bucketname
  215. self.localpath = localpath
  216. self.index = index
  217. self.error_doc = error_doc
  218. def to_config(self):
  219. return {"bucketname":self.bucketname,
  220. "localpath":self.localpath,
  221. "index":self.index,
  222. "error_doc":self.error_doc}
  223. @classmethod
  224. def from_config(cls, config_dict):
  225. return cls(**config_dict)
  226. def get_bucket(self):
  227. return self.get_connection().get_bucket(self.bucketname)
  228. def get_connection(self):
  229. return s3_util.get_connection()
  230. def create(self, use_existing_bucket=False):
  231. """Create the bucket for the subdomain."""
  232. #Check if the bucket name already exists in our account,
  233. #boto doesn't tell us this.
  234. connection = self.get_connection()
  235. if use_existing_bucket:
  236. bucket = connection.get_bucket(self.bucketname)
  237. else:
  238. if self.bucketname in s3_util.get_bucket_names(connection):
  239. raise Exception("Bucket '{0}' already exists in your account."\
  240. .format(self.bucketname))
  241. bucket = connection.create_bucket(self.bucketname)
  242. logger.info("Created new bucket : {0}".format(self.bucketname))
  243. #A website should be publically readable:
  244. bucket.set_acl("public-read")
  245. #Turn on website functionality:
  246. if self.error_doc:
  247. bucket.configure_website(self.index, self.error_doc)
  248. else:
  249. bucket.configure_website(self.index)
  250. def synchronize(self, delete=False):
  251. """Synchronize the localpath to S3.
  252. Upload new or changed files.
  253. Delete files that no longer exist locally."""
  254. bucket = self.get_bucket()
  255. s3_paths = s3_util.get_paths_from_keys(bucket)
  256. local_files = set()
  257. for dirpath, dirnames, filenames in os.walk(self.localpath):
  258. for filename in filenames:
  259. file_path = os.path.join(dirpath,filename)
  260. file_key = os.path.relpath(file_path,self.localpath)
  261. if os.sep == "\\":
  262. #Windows paths need conversion
  263. file_key = file_key.replace("\\","/")
  264. local_files.add(file_key)
  265. try:
  266. s3_key = s3_paths[file_key]
  267. except KeyError:
  268. #File is new
  269. s3_key = bucket.new_key(file_key)
  270. logger.info("Uploading new file: {0}".format(file_key))
  271. s3_key.set_contents_from_filename(file_path)
  272. s3_key.set_acl("public-read")
  273. else:
  274. #File already exists, check if it's changed.
  275. local_md5 = util.md5_for_file(file_path)
  276. if local_md5 != s3_key.etag.replace("\"",""):
  277. #File has changed
  278. logger.info("Uploading changed file: {0}".format(file_key))
  279. s3_key.set_contents_from_filename(file_path)
  280. s3_key.set_acl("public-read")
  281. if delete:
  282. #Delete all files that don't exist locally
  283. for name, key in s3_paths.items():
  284. if name not in local_files:
  285. #Delete it.
  286. logger.info("Deleting old file: {0}".format(name))
  287. key.delete()