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 |