Make our test farm tests instances self-destruct
diff --git a/tests/letstest/auto_targets.yaml b/tests/letstest/auto_targets.yaml
index 9d97c6a8331..01d410227d1 100644
--- a/tests/letstest/auto_targets.yaml
+++ b/tests/letstest/auto_targets.yaml
@@ -31,10 +31,6 @@ targets:
     virt: hvm
     user: admin
     machine_type: a1.medium
-    # userdata: |
-    #   #cloud-init
-    #   runcmd:
-    #     - [ apt-get, install, -y, curl ]
   #-----------------------------------------------------------------------------
   # Other Redhat Distros
   - ami: ami-0916c408cb02e310b
diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py
index cf9f2899ad7..1a1958bd2e3 100644
--- a/tests/letstest/multitester.py
+++ b/tests/letstest/multitester.py
@@ -147,22 +147,32 @@ def make_instance(ec2_client,
                   keyname,
                   security_group_id,
                   subnet_id,
-                  machine_type='t2.micro',
-                  userdata=""): #userdata contains bash or cloud-init script
+                  self_destruct,
+                  machine_type='t2.micro'):
+    """Creates an instance using the given parameters.
+
+    If self_destruct is True, the instance will be configured to shutdown after
+    1 hour and to terminate itself on shutdown.
+
+    """
     block_device_mappings = _get_block_device_mappings(ec2_client, ami_id)
     tags = [{'Key': 'Name', 'Value': instance_name}]
     tag_spec = [{'ResourceType': 'instance', 'Tags': tags}]
-    return ec2_client.create_instances(
-        BlockDeviceMappings=block_device_mappings,
-        ImageId=ami_id,
-        SecurityGroupIds=[security_group_id],
-        SubnetId=subnet_id,
-        KeyName=keyname,
-        MinCount=1,
-        MaxCount=1,
-        UserData=userdata,
-        InstanceType=machine_type,
-        TagSpecifications=tag_spec)[0]
+    kwargs = {
+        'BlockDeviceMappings': block_device_mappings,
+        'ImageId': ami_id,
+        'SecurityGroupIds': [security_group_id],
+        'SubnetId': subnet_id,
+        'KeyName': keyname,
+        'MinCount': 1,
+        'MaxCount': 1,
+        'InstanceType': machine_type,
+        'TagSpecifications': tag_spec
+    }
+    if self_destruct:
+            kwargs['InstanceInitiatedShutdownBehavior'] = 'terminate'
+            kwargs['UserData'] = '#!/bin/bash\nshutdown -P +60\n'
+    return ec2_client.create_instances(**kwargs)[0]
 
 def _get_block_device_mappings(ec2_client, ami_id):
     """Returns the list of block device mappings to ensure cleanup.
@@ -313,7 +323,7 @@ def grab_certbot_log(cxn):
         'cat ./certbot.log; else echo "[nolocallog]"; fi\'')
 
 
-def create_client_instance(ec2_client, target, security_group_id, subnet_id):
+def create_client_instance(ec2_client, target, security_group_id, subnet_id, self_destruct):
     """Create a single client instance for running tests."""
     if 'machine_type' in target:
         machine_type = target['machine_type']
@@ -322,10 +332,6 @@ def create_client_instance(ec2_client, target, security_group_id, subnet_id):
     else:
         # 32 bit systems
         machine_type = 'c1.medium'
-    if 'userdata' in target:
-        userdata = target['userdata']
-    else:
-        userdata = ''
     name = 'le-%s'%target['name']
     print(name, end=" ")
     return make_instance(ec2_client,
@@ -335,7 +341,7 @@ def create_client_instance(ec2_client, target, security_group_id, subnet_id):
                          machine_type=machine_type,
                          security_group_id=security_group_id,
                          subnet_id=subnet_id,
-                         userdata=userdata)
+                         self_destruct=self_destruct)
 
 
 def test_client_process(fab_config, inqueue, outqueue, boulder_url, log_dir):
@@ -490,6 +496,9 @@ def main():
         boulder_preexists = True
     else:
         print("Can't find a boulder server, starting one...")
+        # If we want to kill boulder on shutdown, have it self-destruct in case
+        # cleanup fails.
+        self_destruct = cl_args.killboulder
         boulder_server = make_instance(ec2_client,
                                        'le-boulderserver',
                                        BOULDER_AMI,
@@ -497,16 +506,20 @@ def main():
                                        machine_type='t2.micro',
                                        #machine_type='t2.medium',
                                        security_group_id=security_group_id,
-                                       subnet_id=subnet_id)
+                                       subnet_id=subnet_id,
+                                       self_destruct=self_destruct)
 
     instances = []
     try:
         if not cl_args.boulderonly:
             print("Creating instances: ", end="")
+            # If we want to preserve instances, do not have them self-destruct.
+            self_destruct = not cl_args.saveinstances
             for target in targetlist:
                 instances.append(
                     create_client_instance(ec2_client, target,
-                                           security_group_id, subnet_id)
+                                           security_group_id, subnet_id,
+                                           self_destruct)
                 )
             print()
 

Fixes https://github.com/certbot/certbot/issues/7567.

I used the first approach I described at https://github.com/certbot/certbot/issues/7567#issuecomment-743336813 which was to:

1. Change the instance shutdown behavior so shutting down the instance terminates it. See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior.
2. Add a shell script in user data that runs on machine startup as described at https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts. This shell script schedules the machine to power off in 60 minutes.

To help accomplish this, I made `multitester.py` no longer accept `UserData` in our "target" YAML files, but we weren't using this functionality anyway.

I tested this by locally commenting out the usual cleanup code. After the script exited, I checked the AWS console and the instances were still running, but after the allotted time, they correctly terminated themselves.

With this change, we could delete the normal termination code, but I personally think it's worth keeping around. This scheduled shutdown is a little odd and in the normal case I think we can terminate the instances earlier and maybe save some money.

You can see the test farm tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=3175&view=results.