xdrtest 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!/usr/bin/env python3
  2. #
  3. # Startup/Shutdown Groups of our systems
  4. #
  5. # Common usage:
  6. #
  7. # View the current state of the things on a schedule:
  8. # xdrtest status
  9. #
  10. # At the start of your shift:
  11. # xdrtest start --profile mdr-test-c2-gov
  12. #
  13. # At the end of your shift:
  14. # xdrtest stop
  15. #
  16. import argparse
  17. import boto3
  18. import json
  19. import logging
  20. import os
  21. import re
  22. import sys
  23. DEBUG=False # Turn on with --debug
  24. logger = logging.getLogger()
  25. def query_yes_no(question, default="yes"):
  26. """Ask a yes/no question via input() and return their answer.
  27. "question" is a string that is presented to the user.
  28. "default" is the presumed answer if the user just hits <Enter>.
  29. It must be "yes" (the default), "no" or None (meaning
  30. an answer is required of the user).
  31. The "answer" return value is True for "yes" or False for "no".
  32. """
  33. valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
  34. if default is None:
  35. prompt = " [y/n] "
  36. elif default == "yes":
  37. prompt = " [Y/n] "
  38. elif default == "no":
  39. prompt = " [y/N] "
  40. else:
  41. raise ValueError("invalid default answer: '%s'" % default)
  42. while True:
  43. sys.stdout.write(question + prompt)
  44. choice = input().lower()
  45. if default is not None and choice == "":
  46. return valid[default]
  47. elif choice in valid:
  48. return valid[choice]
  49. else:
  50. sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
  51. def gather_profiles(file='~/.aws/config', commercial=False):
  52. file=os.path.expanduser(file)
  53. profiles=list()
  54. try:
  55. with open(os.path.expanduser(file), "r") as configfile:
  56. for line in configfile:
  57. match = re.search('^\[profile (.*)\]', line)
  58. if match:
  59. if 'test' in match.group(1):
  60. if 'gov' in match.group(1) or commercial:
  61. profiles.append(match.group(1))
  62. except Exception as e:
  63. logger.error(f'Could not open file {file} for reading: {e}')
  64. sys.exit(1)
  65. return profiles
  66. def gather_instances(ec2, tagname, tagvalue, exclude_state):
  67. '''
  68. Gathers the instance information from the provided ec2 resource
  69. '''
  70. desired_instances = ec2.instances.filter(
  71. Filters=[
  72. {
  73. 'Name': f'tag:{tagname}',
  74. 'Values': [ f'{tagvalue}' ]
  75. }
  76. ]
  77. )
  78. for i in desired_instances:
  79. name=""
  80. for t in i.tags:
  81. if t['Key']=='Name':
  82. name = t['Value']
  83. logger.debug(f'DEBUG: Discovered instance {i.instance_id}, Name: {name}, State: {i.state["Name"]}')
  84. if i.state['Name'] != exclude_state:
  85. # Why an iterator? Why not?
  86. yield {
  87. 'instance_id': i.instance_id,
  88. 'name': name,
  89. 'state': i.state['Name']
  90. }
  91. if __name__ == "__main__":
  92. handler = logging.StreamHandler()
  93. logger.addHandler(handler)
  94. # Turn off debugging for AWS
  95. for module in [ 'boto3', 'botocore', 'nose', 's3transfer', 'urllib3', 'urllib3.connectionpool' ]:
  96. l = logging.getLogger(module)
  97. l.setLevel(logging.WARNING) # bump this up if you think you have a boto3 bug
  98. parser = argparse.ArgumentParser(description='Startup/Shutdown Groups of Instances')
  99. parser.add_argument('action', help='Action to Take on Instances', choices=['stop', 'start', 'status'])
  100. parser.add_argument('--config', help='AWS Config File', default='~/.aws/config')
  101. parser.add_argument('--profile', help='Profile to Use', default=None)
  102. parser.add_argument('--tagname', help='Tag Name to Filter On', default='Schedule')
  103. parser.add_argument('tagvalue', help='Tag Value to Filter On', nargs='?', default='MSOC')
  104. parser.add_argument('--commercial', help='Include Commercial Accounts', action='store_true')
  105. parser.add_argument('--dry-run', help='Dry Run', action='store_true')
  106. parser.add_argument('--debug', help='Output Debug Information', action='store_true')
  107. args = parser.parse_args()
  108. if args.debug:
  109. DEBUG=True
  110. logger.setLevel(logging.DEBUG) # Debug
  111. else:
  112. logger.setLevel(logging.INFO) # Info
  113. if args.profile is not None:
  114. profiles=[ args.profile ]
  115. else:
  116. profiles=gather_profiles(file=args.config, commercial=args.commercial)
  117. logger.debug(f'Profiles to gather: {json.dumps(profiles)}')
  118. # Connect to our profiles:
  119. session = dict()
  120. ec2 = dict()
  121. instances = dict()
  122. instance_count = 0
  123. print(f'Instances to Start:')
  124. for p in profiles:
  125. logger.debug(f'Gathering from profile {p}...')
  126. try:
  127. session[p] = boto3.session.Session(profile_name=p)
  128. ec2[p] = session[p].resource('ec2')
  129. except Exception as e:
  130. logger.error(f'Could not connect to profile "{p}": {e}')
  131. exclude_state = ""
  132. if args.action=="start":
  133. exclude_state="running"
  134. elif args.action=="stop":
  135. exclude_state="stopped"
  136. instances[p] = list()
  137. for instance in gather_instances(ec2[p], args.tagname, args.tagvalue, exclude_state):
  138. print(f'{p:<30}\t{ instance["instance_id"] }\t{instance["state"]:<10}\t{instance["name"]}')
  139. instances[p].append(instance['instance_id'])
  140. instance_count += 1
  141. if instance_count == 0:
  142. print('')
  143. print('No applicable instances discovered.')
  144. sys.exit(0)
  145. logger.debug(f'Num instances: {len(instances)}: {json.dumps(instances)}')
  146. if args.action=="status":
  147. logger.debug('Exiting after status check')
  148. sys.exit(0)
  149. print('')
  150. if query_yes_no(question='Continue?', default='no') is False:
  151. logger.debug('Exiting on user request')
  152. sys.exit(2)
  153. for p in profiles:
  154. if len(instances[p]) > 0:
  155. for i in ec2[p].instances.filter(InstanceIds=instances[p]):
  156. if args.action == 'start':
  157. try:
  158. i.start(DryRun=args.dry_run)
  159. except Exception as e:
  160. print(f'An error occured while starting instance {i.id}. Error: {e}. Skipping.')
  161. elif args.action == 'stop':
  162. try:
  163. i.stop(DryRun=args.dry_run)
  164. except Exception as e:
  165. print(f'An error occured while stopping instance {i.id}. Error: {e}. Skipping.')
  166. else:
  167. print(f'Unsupported action: {args.action}')
  168. sys.exit(3)