contrib/automation/hgautomation/aws.py
changeset 45942 89a2afe31e82
parent 44739 828d3277618a
child 47195 546e812a1c2d
equal deleted inserted replaced
45941:346af7687c6f 45942:89a2afe31e82
   150 }
   150 }
   151 '''.strip()
   151 '''.strip()
   152 
   152 
   153 
   153 
   154 IAM_INSTANCE_PROFILES = {
   154 IAM_INSTANCE_PROFILES = {
   155     'ephemeral-ec2-1': {'roles': ['ephemeral-ec2-role-1',],}
   155     'ephemeral-ec2-1': {
       
   156         'roles': [
       
   157             'ephemeral-ec2-role-1',
       
   158         ],
       
   159     }
   156 }
   160 }
   157 
   161 
   158 
   162 
   159 # User Data for Windows EC2 instance. Mainly used to set the password
   163 # User Data for Windows EC2 instance. Mainly used to set the password
   160 # and configure WinRM.
   164 # and configure WinRM.
   467 def find_image(ec2resource, owner_id, name, reverse_sort_field=None):
   471 def find_image(ec2resource, owner_id, name, reverse_sort_field=None):
   468     """Find an AMI by its owner ID and name."""
   472     """Find an AMI by its owner ID and name."""
   469 
   473 
   470     images = ec2resource.images.filter(
   474     images = ec2resource.images.filter(
   471         Filters=[
   475         Filters=[
   472             {'Name': 'owner-id', 'Values': [owner_id],},
   476             {
   473             {'Name': 'state', 'Values': ['available'],},
   477                 'Name': 'owner-id',
   474             {'Name': 'image-type', 'Values': ['machine'],},
   478                 'Values': [owner_id],
   475             {'Name': 'name', 'Values': [name],},
   479             },
       
   480             {
       
   481                 'Name': 'state',
       
   482                 'Values': ['available'],
       
   483             },
       
   484             {
       
   485                 'Name': 'image-type',
       
   486                 'Values': ['machine'],
       
   487             },
       
   488             {
       
   489                 'Name': 'name',
       
   490                 'Values': [name],
       
   491             },
   476         ]
   492         ]
   477     )
   493     )
   478 
   494 
   479     if reverse_sort_field:
   495     if reverse_sort_field:
   480         images = sorted(
   496         images = sorted(
   517 
   533 
   518         actual = '%s%s' % (prefix, name)
   534         actual = '%s%s' % (prefix, name)
   519         print('adding security group %s' % actual)
   535         print('adding security group %s' % actual)
   520 
   536 
   521         group_res = ec2resource.create_security_group(
   537         group_res = ec2resource.create_security_group(
   522             Description=group['description'], GroupName=actual,
   538             Description=group['description'],
   523         )
   539             GroupName=actual,
   524 
   540         )
   525         group_res.authorize_ingress(IpPermissions=group['ingress'],)
   541 
       
   542         group_res.authorize_ingress(
       
   543             IpPermissions=group['ingress'],
       
   544         )
   526 
   545 
   527         security_groups[name] = group_res
   546         security_groups[name] = group_res
   528 
   547 
   529     return security_groups
   548     return security_groups
   530 
   549 
   612 def wait_for_ssm(ssmclient, instances):
   631 def wait_for_ssm(ssmclient, instances):
   613     """Wait for SSM to come online for an iterable of instance IDs."""
   632     """Wait for SSM to come online for an iterable of instance IDs."""
   614     while True:
   633     while True:
   615         res = ssmclient.describe_instance_information(
   634         res = ssmclient.describe_instance_information(
   616             Filters=[
   635             Filters=[
   617                 {'Key': 'InstanceIds', 'Values': [i.id for i in instances],},
   636                 {
       
   637                     'Key': 'InstanceIds',
       
   638                     'Values': [i.id for i in instances],
       
   639                 },
   618             ],
   640             ],
   619         )
   641         )
   620 
   642 
   621         available = len(res['InstanceInformationList'])
   643         available = len(res['InstanceInformationList'])
   622         wanted = len(instances)
   644         wanted = len(instances)
   634 
   656 
   635     res = ssmclient.send_command(
   657     res = ssmclient.send_command(
   636         InstanceIds=[i.id for i in instances],
   658         InstanceIds=[i.id for i in instances],
   637         DocumentName=document_name,
   659         DocumentName=document_name,
   638         Parameters=parameters,
   660         Parameters=parameters,
   639         CloudWatchOutputConfig={'CloudWatchOutputEnabled': True,},
   661         CloudWatchOutputConfig={
       
   662             'CloudWatchOutputEnabled': True,
       
   663         },
   640     )
   664     )
   641 
   665 
   642     command_id = res['Command']['CommandId']
   666     command_id = res['Command']['CommandId']
   643 
   667 
   644     for instance in instances:
   668     for instance in instances:
   645         while True:
   669         while True:
   646             try:
   670             try:
   647                 res = ssmclient.get_command_invocation(
   671                 res = ssmclient.get_command_invocation(
   648                     CommandId=command_id, InstanceId=instance.id,
   672                     CommandId=command_id,
       
   673                     InstanceId=instance.id,
   649                 )
   674                 )
   650             except botocore.exceptions.ClientError as e:
   675             except botocore.exceptions.ClientError as e:
   651                 if e.response['Error']['Code'] == 'InvocationDoesNotExist':
   676                 if e.response['Error']['Code'] == 'InvocationDoesNotExist':
   652                     print('could not find SSM command invocation; waiting')
   677                     print('could not find SSM command invocation; waiting')
   653                     time.sleep(1)
   678                     time.sleep(1)
   797     Returns the ``ec2resource.Image`` representing the created AMI.
   822     Returns the ``ec2resource.Image`` representing the created AMI.
   798     """
   823     """
   799     instance.stop()
   824     instance.stop()
   800 
   825 
   801     ec2client.get_waiter('instance_stopped').wait(
   826     ec2client.get_waiter('instance_stopped').wait(
   802         InstanceIds=[instance.id], WaiterConfig={'Delay': 5,}
   827         InstanceIds=[instance.id],
       
   828         WaiterConfig={
       
   829             'Delay': 5,
       
   830         },
   803     )
   831     )
   804     print('%s is stopped' % instance.id)
   832     print('%s is stopped' % instance.id)
   805 
   833 
   806     image = instance.create_image(Name=name, Description=description,)
   834     image = instance.create_image(
       
   835         Name=name,
       
   836         Description=description,
       
   837     )
   807 
   838 
   808     image.create_tags(
   839     image.create_tags(
   809         Tags=[{'Key': 'HGIMAGEFINGERPRINT', 'Value': fingerprint,},]
   840         Tags=[
       
   841             {
       
   842                 'Key': 'HGIMAGEFINGERPRINT',
       
   843                 'Value': fingerprint,
       
   844             },
       
   845         ]
   810     )
   846     )
   811 
   847 
   812     print('waiting for image %s' % image.id)
   848     print('waiting for image %s' % image.id)
   813 
   849 
   814     ec2client.get_waiter('image_available').wait(ImageIds=[image.id],)
   850     ec2client.get_waiter('image_available').wait(
       
   851         ImageIds=[image.id],
       
   852     )
   815 
   853 
   816     print('image %s available as %s' % (image.id, image.name))
   854     print('image %s available as %s' % (image.id, image.name))
   817 
   855 
   818     return image
   856     return image
   819 
   857 
   835             'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994',
   873             'debian-stretch-hvm-x86_64-gp2-2019-09-08-17994',
   836         )
   874         )
   837         ssh_username = 'admin'
   875         ssh_username = 'admin'
   838     elif distro == 'debian10':
   876     elif distro == 'debian10':
   839         image = find_image(
   877         image = find_image(
   840             ec2resource, DEBIAN_ACCOUNT_ID_2, 'debian-10-amd64-20190909-10',
   878             ec2resource,
       
   879             DEBIAN_ACCOUNT_ID_2,
       
   880             'debian-10-amd64-20190909-10',
   841         )
   881         )
   842         ssh_username = 'admin'
   882         ssh_username = 'admin'
   843     elif distro == 'ubuntu18.04':
   883     elif distro == 'ubuntu18.04':
   844         image = find_image(
   884         image = find_image(
   845             ec2resource,
   885             ec2resource,
  1064             for instance in instances:
  1104             for instance in instances:
  1065                 instance.ssh_client.close()
  1105                 instance.ssh_client.close()
  1066 
  1106 
  1067 
  1107 
  1068 def ensure_windows_dev_ami(
  1108 def ensure_windows_dev_ami(
  1069     c: AWSConnection, prefix='hg-', base_image_name=WINDOWS_BASE_IMAGE_NAME,
  1109     c: AWSConnection,
       
  1110     prefix='hg-',
       
  1111     base_image_name=WINDOWS_BASE_IMAGE_NAME,
  1070 ):
  1112 ):
  1071     """Ensure Windows Development AMI is available and up-to-date.
  1113     """Ensure Windows Development AMI is available and up-to-date.
  1072 
  1114 
  1073     If necessary, a modern AMI will be built by starting a temporary EC2
  1115     If necessary, a modern AMI will be built by starting a temporary EC2
  1074     instance and bootstrapping it.
  1116     instance and bootstrapping it.
  1188         print('installing Windows features...')
  1230         print('installing Windows features...')
  1189         run_ssm_command(
  1231         run_ssm_command(
  1190             ssmclient,
  1232             ssmclient,
  1191             [instance],
  1233             [instance],
  1192             'AWS-RunPowerShellScript',
  1234             'AWS-RunPowerShellScript',
  1193             {'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),},
  1235             {
       
  1236                 'commands': WINDOWS_BOOTSTRAP_POWERSHELL.split('\n'),
       
  1237             },
  1194         )
  1238         )
  1195 
  1239 
  1196         # Reboot so all updates are fully applied.
  1240         # Reboot so all updates are fully applied.
  1197         #
  1241         #
  1198         # We don't use instance.reboot() here because it is asynchronous and
  1242         # We don't use instance.reboot() here because it is asynchronous and
  1200         # a while to stop and we may start trying to interact with the instance
  1244         # a while to stop and we may start trying to interact with the instance
  1201         # before it has rebooted.
  1245         # before it has rebooted.
  1202         print('rebooting instance %s' % instance.id)
  1246         print('rebooting instance %s' % instance.id)
  1203         instance.stop()
  1247         instance.stop()
  1204         ec2client.get_waiter('instance_stopped').wait(
  1248         ec2client.get_waiter('instance_stopped').wait(
  1205             InstanceIds=[instance.id], WaiterConfig={'Delay': 5,}
  1249             InstanceIds=[instance.id],
       
  1250             WaiterConfig={
       
  1251                 'Delay': 5,
       
  1252             },
  1206         )
  1253         )
  1207 
  1254 
  1208         instance.start()
  1255         instance.start()
  1209         wait_for_ip_addresses([instance])
  1256         wait_for_ip_addresses([instance])
  1210 
  1257