We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove Trusty-specific logic from application code and dev env
## Description

Some platform-specific logic was added to handle the upgrade from Trusty to Xenial and should be removed:
- Source Interface Submission disable logic: https://github.com/freedomofpress/securedrop/pull/4325
- Admin Interface alert for end-of-life OS: https://github.com/freedomofpress/securedrop/pull/4055

There is also some Trusty-specific logic in the development environment: 
- [Docker images](https://github.com/freedomofpress/securedrop/tree/develop/securedrop/dockerfiles/trusty)
- [CI targets](https://github.com/freedomofpress/securedrop/blob/develop/.circleci/config.yml#L59
- The use of `BASE_OS` in Makefile targets
- Upgrade testing scenarios and boxes

Since Trusty will no longer be supported after 0.12.2, we should remove this logic to improve the maintainability of the codebase and reduce the risk of errors.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `molecule/vagrant-packager/package.py`
Content:
```
1 #!/usr/bin/env python3
2 #
3 #
4 #
5 #
6 import hashlib
7 import json
8 import os
9 from os.path import join
10 import re
11 import shutil
12 import subprocess
13 import tarfile
14 import xml.etree.ElementTree as ET
15 
16 
17 # Current script is symlinked into adjacent scenario, for Trusty compatibility.
18 # Look up "name" for scenario from real path (relative to symlink), but store
19 # all artifacts in primary scenario (via realpath).
20 SCENARIO_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
21 SCENARIO_PATH = os.path.dirname(os.path.realpath(__file__))
22 BOX_BUILD_DIR = join(SCENARIO_PATH, "build")
23 BOX_METADATA_DIR = join(SCENARIO_PATH, "box_files")
24 EPHEMERAL_DIRS = {}
25 TARGET_VERSION_FILE = os.path.join(SCENARIO_PATH, os.path.pardir, "shared", "stable.ver")
26 
27 
28 class LibVirtPackager(object):
29 
30     def __init__(self, vm):
31         # type: (str) -> None
32         self.cli_prefix = "virsh --connect qemu:///system {}"
33         self.vm_name = vm
34 
35     def _get_virsh_xml(self, cmd):
36         # type: (str) -> ET.Element
37         virsh_cmd_str = self.cli_prefix.format(cmd)
38         cmd_output = subprocess.check_output(virsh_cmd_str.split())
39         return ET.fromstring(cmd_output)
40 
41     def vm_xml(self):
42         # type: () -> ET.Element
43         """ Get XML definition for virtual machine domain
44         """
45         return self._get_virsh_xml("dumpxml "+self.vm_name)
46 
47     def default_image_location(self):
48         # type: () -> str
49         """
50             Get full system path to the default system pool dir
51         """
52         pool = self._get_virsh_xml("pool-dumpxml default")
53 
54         return pool.findall('./target/path')[0].text
55 
56     def image_rebase(self, img_location):
57         # type: (str) -> None
58         """ If an image has a backing store, merge the backing store into the
59             target image file
60         """
61         if self.default_image_location() in img_location:
62             raise UserWarning("To prevent catastrophy, will not"
63                               " run on image in the default pool. Copy it"
64                               " first to someplace else")
65 
66         img_info = subprocess.check_output(["qemu-img", "info", img_location])
67         rebase_cmd = """qemu-img rebase -b "" {}""".format(img_location)
68 
69         if "backing file:" in img_info.decode('utf-8'):
70             print("Running rebase now..")
71             subprocess.check_call(rebase_cmd, shell=True)
72         else:
73             print("No rebase necessary")
74 
75     def image_store_path(self):
76         # type: () -> str
77         """ Get location of VM's first storage disk file """
78         vm_xml = self.vm_xml()
79         return vm_xml.findall('./devices/disk/source')[0].attrib['file']
80 
81     def image_sparsify(self, src, dest, tmp_dir, inplace):
82         # type: (str, str, str, bool) -> None
83         """ Remove blank-space from the image. Note that setting inplace to
84             false will get you better bang for your buck but can make system
85             unstable. Pushed IO to the max on my machine and caused it to crash
86         """
87         img_info = subprocess.check_output(["qemu-img", "info", src])
88 
89         if "backing file:" in img_info.decode('utf-8'):
90             raise UserWarning("Cannot sparsify image w/ backing "
91                               "store. Please rebase first.")
92 
93         if inplace:
94             subprocess.check_call(["virt-sparsify",
95                                    "--in-place",
96                                    src])
97             shutil.move(src, dest)
98         else:
99             subprocess.check_call(["virt-sparsify", "--tmp",
100                                   tmp_dir,
101                                   src,
102                                   dest])
103 
104     def sysprep(self, img_location):
105         # type: (str) -> None
106         """ Run the virt-sysprep tool over the image to prep the log for
107             re-dist. Removes things like logs and user history files
108         """
109         sysprep_cmd = ("virt-sysprep --no-logfile --operations "
110                        "defaults,-ssh-userdir,-ssh-hostkeys,-logfiles -a " +
111                        img_location)
112         subprocess.check_call(sysprep_cmd.split())
113 
114     def vagrant_metadata(self, img_location):
115         # type: (str) -> dict
116         """ Produce dictionary of necessary vagrant key/values """
117         json = {}
118 
119         info_output = subprocess.check_output(["qemu-img", "info",
120                                                img_location]).decode('utf-8')
121         json['virtual_size'] = int((re.search("virtual size: (?P<size>\d+)G",
122                                     info_output)).group("size"))
123 
124         json['format'] = (re.search("file format: (?P<format>\w+)",
125                                     info_output)).group("format")
126         json['provider'] = 'libvirt'
127 
128         return json
129 
130 
131 def main():
132     with open(TARGET_VERSION_FILE, 'r') as f:
133         TARGET_VERSION = f.read().strip()
134 
135     # Default to Xenial as base OS, but detect if script was invoked from the
136     # Trusty-specific scenario, and use Trusty if so.
137     TARGET_PLATFORM = os.environ.get("SECUREDROP_TARGET_PLATFORM", "xenial")
138 
139     for srv in ["app-staging", "mon-staging"]:
140 
141         for temp_dir in ["build", "tmp"]:
142             try:
143                 ephemeral_path = join(SCENARIO_PATH, ".molecule",
144                                       temp_dir)
145                 EPHEMERAL_DIRS[temp_dir] = ephemeral_path
146 
147                 os.makedirs(os.path.join(SCENARIO_PATH, ".molecule", temp_dir))
148             except OSError:
149                 pass
150 
151         vm = LibVirtPackager("{}_{}".format(SCENARIO_NAME, srv))
152 
153         tmp_img_file = join(EPHEMERAL_DIRS["tmp"], "wip.img")
154         packaged_img_file = join(EPHEMERAL_DIRS["build"], "box.img")
155 
156         print("Copying VM image store locally")
157         subprocess.check_output(["sudo", "cp",
158                                 vm.image_store_path(),  # source
159                                 tmp_img_file  # dest
160                                  ])
161 
162         print("Changing file ownership")
163         subprocess.check_output(["sudo", "chown", os.environ['USER'],
164                                 tmp_img_file])
165 
166         # Run a sysprep on it
167         print("Run an image sysprep")
168         vm.sysprep(tmp_img_file)
169 
170         print("Rebase Image")
171         vm.image_rebase(tmp_img_file)
172 
173         # Sparsify the image file
174         print("Run sparsi-fication on the image")
175         vm.image_sparsify(src=tmp_img_file,
176                           dest=packaged_img_file,
177                           tmp_dir=EPHEMERAL_DIRS['tmp'],
178                           inplace=True)
179 
180         # Write out metadata file
181         with open(join(EPHEMERAL_DIRS['build'], 'metadata.json'),
182                   'w') as mdata:
183             json.dump(
184                 vm.vagrant_metadata(packaged_img_file),
185                 mdata)
186 
187         # Copy in appropriate vagrant file to build dir
188         shutil.copyfile(join(BOX_METADATA_DIR, "Vagrantfile."+srv),
189                         join(EPHEMERAL_DIRS['build'], 'Vagrantfile'))
190 
191         print("Creating tar file")
192         box_file = join(BOX_BUILD_DIR, "{}-{}_{}.box".format(srv, TARGET_PLATFORM, TARGET_VERSION))
193         with tarfile.open(box_file, "w|gz") as tar:
194             for boxfile in ["box.img", "Vagrantfile", "metadata.json"]:
195                 tar.add(join(EPHEMERAL_DIRS["build"], boxfile),
196                         arcname=boxfile)
197 
198         print("Box created at {}".format(box_file))
199 
200         print("Updating box metadata")
201         update_box_metadata(srv, box_file, TARGET_PLATFORM, TARGET_VERSION)
202 
203         print("Clean-up tmp space")
204         shutil.rmtree(EPHEMERAL_DIRS['tmp'])
205 
206 
207 def sha256_checksum(filepath):
208     """
209     Returns a SHA256 checksum for a given filepath.
210     """
211     checksum = hashlib.sha256()
212     with open(filepath, 'rb') as f:
213         # Read by chunks, to avoid slurping the entire file into memory.
214         # Box files range from 500MB to 1.5GB.
215         for block in iter(lambda: f.read(checksum.block_size), b''):
216             checksum.update(block)
217     return checksum.hexdigest()
218 
219 
220 def update_box_metadata(server_name, box_file, platform, version):
221     """
222     Updates the JSON file of Vagrant box metadata, including remote URL,
223     version number, and SHA256 checksum.
224     """
225     # Strip off "staging" suffix from box names
226     server_name_short = re.sub('\-staging$', '', server_name)
227     json_file_basename = "{}_{}_metadata.json".format(server_name_short, platform)
228     json_file = os.path.join(BOX_METADATA_DIR, json_file_basename)
229 
230     # Read in current JSON metadata, so we can append the new info to it.
231     with open(json_file, "r") as f:
232         metadata_config = json.loads(f.read())
233 
234     base_url = "https://dev-bin.ops.securedrop.org/vagrant"
235     box_name = os.path.basename(box_file)
236     box_url = "{}/{}".format(base_url, box_name)
237     box_checksum = sha256_checksum(box_file)
238     box_config = dict(
239         name="libvirt",
240         url=box_url,
241         checksum_type="sha256",
242         checksum=box_checksum,
243     )
244     # Creating list of dicts to adhere to JSON format of Vagrant box metadata
245     providers_list = []
246     providers_list.append(box_config)
247     version_config = dict(
248         version=version,
249         providers=providers_list,
250     )
251     box_versions = metadata_config['versions']
252     box_versions.append(version_config)
253     metadata_config['versions'] = box_versions
254 
255     # Write out final, modified data. Does not validate for uniqueness,
256     # so repeated runs on the same version will duplicate version info,
257     # which'll likely break the box fetching. Target file is version-controlled,
258     # though, so easy enough to correct in the event of a mistake.
259     with open(json_file, "w") as f:
260         f.write(json.dumps(metadata_config, indent=2, sort_keys=True))
261 
262 
263 if __name__ == "__main__":
264     main()
265
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/molecule/vagrant-packager/package.py b/molecule/vagrant-packager/package.py
--- a/molecule/vagrant-packager/package.py
+++ b/molecule/vagrant-packager/package.py
@@ -14,9 +14,6 @@
 import xml.etree.ElementTree as ET
 
 
-# Current script is symlinked into adjacent scenario, for Trusty compatibility.
-# Look up "name" for scenario from real path (relative to symlink), but store
-# all artifacts in primary scenario (via realpath).
 SCENARIO_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
 SCENARIO_PATH = os.path.dirname(os.path.realpath(__file__))
 BOX_BUILD_DIR = join(SCENARIO_PATH, "build")
@@ -132,8 +129,7 @@
     with open(TARGET_VERSION_FILE, 'r') as f:
         TARGET_VERSION = f.read().strip()
 
-    # Default to Xenial as base OS, but detect if script was invoked from the
-    # Trusty-specific scenario, and use Trusty if so.
+    # Default to Xenial as base OS.
     TARGET_PLATFORM = os.environ.get("SECUREDROP_TARGET_PLATFORM", "xenial")
 
     for srv in ["app-staging", "mon-staging"]:

{"golden_diff": "diff --git a/molecule/vagrant-packager/package.py b/molecule/vagrant-packager/package.py\n--- a/molecule/vagrant-packager/package.py\n+++ b/molecule/vagrant-packager/package.py\n@@ -14,9 +14,6 @@\n import xml.etree.ElementTree as ET\n \n \n-# Current script is symlinked into adjacent scenario, for Trusty compatibility.\n-# Look up \"name\" for scenario from real path (relative to symlink), but store\n-# all artifacts in primary scenario (via realpath).\n SCENARIO_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))\n SCENARIO_PATH = os.path.dirname(os.path.realpath(__file__))\n BOX_BUILD_DIR = join(SCENARIO_PATH, \"build\")\n@@ -132,8 +129,7 @@\n     with open(TARGET_VERSION_FILE, 'r') as f:\n         TARGET_VERSION = f.read().strip()\n \n-    # Default to Xenial as base OS, but detect if script was invoked from the\n-    # Trusty-specific scenario, and use Trusty if so.\n+    # Default to Xenial as base OS.\n     TARGET_PLATFORM = os.environ.get(\"SECUREDROP_TARGET_PLATFORM\", \"xenial\")\n \n     for srv in [\"app-staging\", \"mon-staging\"]:\n", "issue": "Remove Trusty-specific logic from application code and dev env\n## Description\r\n\r\nSome platform-specific logic was added to handle the upgrade from Trusty to Xenial and should be removed:\r\n- Source Interface Submission disable logic: https://github.com/freedomofpress/securedrop/pull/4325\r\n- Admin Interface alert for end-of-life OS: https://github.com/freedomofpress/securedrop/pull/4055\r\n\r\nThere is also some Trusty-specific logic in the development environment: \r\n- [Docker images](https://github.com/freedomofpress/securedrop/tree/develop/securedrop/dockerfiles/trusty)\r\n- [CI targets](https://github.com/freedomofpress/securedrop/blob/develop/.circleci/config.yml#L59\r\n- The use of `BASE_OS` in Makefile targets\r\n- Upgrade testing scenarios and boxes\r\n\r\nSince Trusty will no longer be supported after 0.12.2, we should remove this logic to improve the maintainability of the codebase and reduce the risk of errors.\n", "before_files": [{"content": "#!/usr/bin/env python3\n#\n#\n#\n#\nimport hashlib\nimport json\nimport os\nfrom os.path import join\nimport re\nimport shutil\nimport subprocess\nimport tarfile\nimport xml.etree.ElementTree as ET\n\n\n# Current script is symlinked into adjacent scenario, for Trusty compatibility.\n# Look up \"name\" for scenario from real path (relative to symlink), but store\n# all artifacts in primary scenario (via realpath).\nSCENARIO_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))\nSCENARIO_PATH = os.path.dirname(os.path.realpath(__file__))\nBOX_BUILD_DIR = join(SCENARIO_PATH, \"build\")\nBOX_METADATA_DIR = join(SCENARIO_PATH, \"box_files\")\nEPHEMERAL_DIRS = {}\nTARGET_VERSION_FILE = os.path.join(SCENARIO_PATH, os.path.pardir, \"shared\", \"stable.ver\")\n\n\nclass LibVirtPackager(object):\n\n    def __init__(self, vm):\n        # type: (str) -> None\n        self.cli_prefix = \"virsh --connect qemu:///system {}\"\n        self.vm_name = vm\n\n    def _get_virsh_xml(self, cmd):\n        # type: (str) -> ET.Element\n        virsh_cmd_str = self.cli_prefix.format(cmd)\n        cmd_output = subprocess.check_output(virsh_cmd_str.split())\n        return ET.fromstring(cmd_output)\n\n    def vm_xml(self):\n        # type: () -> ET.Element\n        \"\"\" Get XML definition for virtual machine domain\n        \"\"\"\n        return self._get_virsh_xml(\"dumpxml \"+self.vm_name)\n\n    def default_image_location(self):\n        # type: () -> str\n        \"\"\"\n            Get full system path to the default system pool dir\n        \"\"\"\n        pool = self._get_virsh_xml(\"pool-dumpxml default\")\n\n        return pool.findall('./target/path')[0].text\n\n    def image_rebase(self, img_location):\n        # type: (str) -> None\n        \"\"\" If an image has a backing store, merge the backing store into the\n            target image file\n        \"\"\"\n        if self.default_image_location() in img_location:\n            raise UserWarning(\"To prevent catastrophy, will not\"\n                              \" run on image in the default pool. Copy it\"\n                              \" first to someplace else\")\n\n        img_info = subprocess.check_output([\"qemu-img\", \"info\", img_location])\n        rebase_cmd = \"\"\"qemu-img rebase -b \"\" {}\"\"\".format(img_location)\n\n        if \"backing file:\" in img_info.decode('utf-8'):\n            print(\"Running rebase now..\")\n            subprocess.check_call(rebase_cmd, shell=True)\n        else:\n            print(\"No rebase necessary\")\n\n    def image_store_path(self):\n        # type: () -> str\n        \"\"\" Get location of VM's first storage disk file \"\"\"\n        vm_xml = self.vm_xml()\n        return vm_xml.findall('./devices/disk/source')[0].attrib['file']\n\n    def image_sparsify(self, src, dest, tmp_dir, inplace):\n        # type: (str, str, str, bool) -> None\n        \"\"\" Remove blank-space from the image. Note that setting inplace to\n            false will get you better bang for your buck but can make system\n            unstable. Pushed IO to the max on my machine and caused it to crash\n        \"\"\"\n        img_info = subprocess.check_output([\"qemu-img\", \"info\", src])\n\n        if \"backing file:\" in img_info.decode('utf-8'):\n            raise UserWarning(\"Cannot sparsify image w/ backing \"\n                              \"store. Please rebase first.\")\n\n        if inplace:\n            subprocess.check_call([\"virt-sparsify\",\n                                   \"--in-place\",\n                                   src])\n            shutil.move(src, dest)\n        else:\n            subprocess.check_call([\"virt-sparsify\", \"--tmp\",\n                                  tmp_dir,\n                                  src,\n                                  dest])\n\n    def sysprep(self, img_location):\n        # type: (str) -> None\n        \"\"\" Run the virt-sysprep tool over the image to prep the log for\n            re-dist. Removes things like logs and user history files\n        \"\"\"\n        sysprep_cmd = (\"virt-sysprep --no-logfile --operations \"\n                       \"defaults,-ssh-userdir,-ssh-hostkeys,-logfiles -a \" +\n                       img_location)\n        subprocess.check_call(sysprep_cmd.split())\n\n    def vagrant_metadata(self, img_location):\n        # type: (str) -> dict\n        \"\"\" Produce dictionary of necessary vagrant key/values \"\"\"\n        json = {}\n\n        info_output = subprocess.check_output([\"qemu-img\", \"info\",\n                                               img_location]).decode('utf-8')\n        json['virtual_size'] = int((re.search(\"virtual size: (?P<size>\\d+)G\",\n                                    info_output)).group(\"size\"))\n\n        json['format'] = (re.search(\"file format: (?P<format>\\w+)\",\n                                    info_output)).group(\"format\")\n        json['provider'] = 'libvirt'\n\n        return json\n\n\ndef main():\n    with open(TARGET_VERSION_FILE, 'r') as f:\n        TARGET_VERSION = f.read().strip()\n\n    # Default to Xenial as base OS, but detect if script was invoked from the\n    # Trusty-specific scenario, and use Trusty if so.\n    TARGET_PLATFORM = os.environ.get(\"SECUREDROP_TARGET_PLATFORM\", \"xenial\")\n\n    for srv in [\"app-staging\", \"mon-staging\"]:\n\n        for temp_dir in [\"build\", \"tmp\"]:\n            try:\n                ephemeral_path = join(SCENARIO_PATH, \".molecule\",\n                                      temp_dir)\n                EPHEMERAL_DIRS[temp_dir] = ephemeral_path\n\n                os.makedirs(os.path.join(SCENARIO_PATH, \".molecule\", temp_dir))\n            except OSError:\n                pass\n\n        vm = LibVirtPackager(\"{}_{}\".format(SCENARIO_NAME, srv))\n\n        tmp_img_file = join(EPHEMERAL_DIRS[\"tmp\"], \"wip.img\")\n        packaged_img_file = join(EPHEMERAL_DIRS[\"build\"], \"box.img\")\n\n        print(\"Copying VM image store locally\")\n        subprocess.check_output([\"sudo\", \"cp\",\n                                vm.image_store_path(),  # source\n                                tmp_img_file  # dest\n                                 ])\n\n        print(\"Changing file ownership\")\n        subprocess.check_output([\"sudo\", \"chown\", os.environ['USER'],\n                                tmp_img_file])\n\n        # Run a sysprep on it\n        print(\"Run an image sysprep\")\n        vm.sysprep(tmp_img_file)\n\n        print(\"Rebase Image\")\n        vm.image_rebase(tmp_img_file)\n\n        # Sparsify the image file\n        print(\"Run sparsi-fication on the image\")\n        vm.image_sparsify(src=tmp_img_file,\n                          dest=packaged_img_file,\n                          tmp_dir=EPHEMERAL_DIRS['tmp'],\n                          inplace=True)\n\n        # Write out metadata file\n        with open(join(EPHEMERAL_DIRS['build'], 'metadata.json'),\n                  'w') as mdata:\n            json.dump(\n                vm.vagrant_metadata(packaged_img_file),\n                mdata)\n\n        # Copy in appropriate vagrant file to build dir\n        shutil.copyfile(join(BOX_METADATA_DIR, \"Vagrantfile.\"+srv),\n                        join(EPHEMERAL_DIRS['build'], 'Vagrantfile'))\n\n        print(\"Creating tar file\")\n        box_file = join(BOX_BUILD_DIR, \"{}-{}_{}.box\".format(srv, TARGET_PLATFORM, TARGET_VERSION))\n        with tarfile.open(box_file, \"w|gz\") as tar:\n            for boxfile in [\"box.img\", \"Vagrantfile\", \"metadata.json\"]:\n                tar.add(join(EPHEMERAL_DIRS[\"build\"], boxfile),\n                        arcname=boxfile)\n\n        print(\"Box created at {}\".format(box_file))\n\n        print(\"Updating box metadata\")\n        update_box_metadata(srv, box_file, TARGET_PLATFORM, TARGET_VERSION)\n\n        print(\"Clean-up tmp space\")\n        shutil.rmtree(EPHEMERAL_DIRS['tmp'])\n\n\ndef sha256_checksum(filepath):\n    \"\"\"\n    Returns a SHA256 checksum for a given filepath.\n    \"\"\"\n    checksum = hashlib.sha256()\n    with open(filepath, 'rb') as f:\n        # Read by chunks, to avoid slurping the entire file into memory.\n        # Box files range from 500MB to 1.5GB.\n        for block in iter(lambda: f.read(checksum.block_size), b''):\n            checksum.update(block)\n    return checksum.hexdigest()\n\n\ndef update_box_metadata(server_name, box_file, platform, version):\n    \"\"\"\n    Updates the JSON file of Vagrant box metadata, including remote URL,\n    version number, and SHA256 checksum.\n    \"\"\"\n    # Strip off \"staging\" suffix from box names\n    server_name_short = re.sub('\\-staging$', '', server_name)\n    json_file_basename = \"{}_{}_metadata.json\".format(server_name_short, platform)\n    json_file = os.path.join(BOX_METADATA_DIR, json_file_basename)\n\n    # Read in current JSON metadata, so we can append the new info to it.\n    with open(json_file, \"r\") as f:\n        metadata_config = json.loads(f.read())\n\n    base_url = \"https://dev-bin.ops.securedrop.org/vagrant\"\n    box_name = os.path.basename(box_file)\n    box_url = \"{}/{}\".format(base_url, box_name)\n    box_checksum = sha256_checksum(box_file)\n    box_config = dict(\n        name=\"libvirt\",\n        url=box_url,\n        checksum_type=\"sha256\",\n        checksum=box_checksum,\n    )\n    # Creating list of dicts to adhere to JSON format of Vagrant box metadata\n    providers_list = []\n    providers_list.append(box_config)\n    version_config = dict(\n        version=version,\n        providers=providers_list,\n    )\n    box_versions = metadata_config['versions']\n    box_versions.append(version_config)\n    metadata_config['versions'] = box_versions\n\n    # Write out final, modified data. Does not validate for uniqueness,\n    # so repeated runs on the same version will duplicate version info,\n    # which'll likely break the box fetching. Target file is version-controlled,\n    # though, so easy enough to correct in the event of a mistake.\n    with open(json_file, \"w\") as f:\n        f.write(json.dumps(metadata_config, indent=2, sort_keys=True))\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "molecule/vagrant-packager/package.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n#\n#\n#\n#\nimport hashlib\nimport json\nimport os\nfrom os.path import join\nimport re\nimport shutil\nimport subprocess\nimport tarfile\nimport xml.etree.ElementTree as ET\n\n\nSCENARIO_NAME = os.path.basename(os.path.dirname(os.path.abspath(__file__)))\nSCENARIO_PATH = os.path.dirname(os.path.realpath(__file__))\nBOX_BUILD_DIR = join(SCENARIO_PATH, \"build\")\nBOX_METADATA_DIR = join(SCENARIO_PATH, \"box_files\")\nEPHEMERAL_DIRS = {}\nTARGET_VERSION_FILE = os.path.join(SCENARIO_PATH, os.path.pardir, \"shared\", \"stable.ver\")\n\n\nclass LibVirtPackager(object):\n\n    def __init__(self, vm):\n        # type: (str) -> None\n        self.cli_prefix = \"virsh --connect qemu:///system {}\"\n        self.vm_name = vm\n\n    def _get_virsh_xml(self, cmd):\n        # type: (str) -> ET.Element\n        virsh_cmd_str = self.cli_prefix.format(cmd)\n        cmd_output = subprocess.check_output(virsh_cmd_str.split())\n        return ET.fromstring(cmd_output)\n\n    def vm_xml(self):\n        # type: () -> ET.Element\n        \"\"\" Get XML definition for virtual machine domain\n        \"\"\"\n        return self._get_virsh_xml(\"dumpxml \"+self.vm_name)\n\n    def default_image_location(self):\n        # type: () -> str\n        \"\"\"\n            Get full system path to the default system pool dir\n        \"\"\"\n        pool = self._get_virsh_xml(\"pool-dumpxml default\")\n\n        return pool.findall('./target/path')[0].text\n\n    def image_rebase(self, img_location):\n        # type: (str) -> None\n        \"\"\" If an image has a backing store, merge the backing store into the\n            target image file\n        \"\"\"\n        if self.default_image_location() in img_location:\n            raise UserWarning(\"To prevent catastrophy, will not\"\n                              \" run on image in the default pool. Copy it\"\n                              \" first to someplace else\")\n\n        img_info = subprocess.check_output([\"qemu-img\", \"info\", img_location])\n        rebase_cmd = \"\"\"qemu-img rebase -b \"\" {}\"\"\".format(img_location)\n\n        if \"backing file:\" in img_info.decode('utf-8'):\n            print(\"Running rebase now..\")\n            subprocess.check_call(rebase_cmd, shell=True)\n        else:\n            print(\"No rebase necessary\")\n\n    def image_store_path(self):\n        # type: () -> str\n        \"\"\" Get location of VM's first storage disk file \"\"\"\n        vm_xml = self.vm_xml()\n        return vm_xml.findall('./devices/disk/source')[0].attrib['file']\n\n    def image_sparsify(self, src, dest, tmp_dir, inplace):\n        # type: (str, str, str, bool) -> None\n        \"\"\" Remove blank-space from the image. Note that setting inplace to\n            false will get you better bang for your buck but can make system\n            unstable. Pushed IO to the max on my machine and caused it to crash\n        \"\"\"\n        img_info = subprocess.check_output([\"qemu-img\", \"info\", src])\n\n        if \"backing file:\" in img_info.decode('utf-8'):\n            raise UserWarning(\"Cannot sparsify image w/ backing \"\n                              \"store. Please rebase first.\")\n\n        if inplace:\n            subprocess.check_call([\"virt-sparsify\",\n                                   \"--in-place\",\n                                   src])\n            shutil.move(src, dest)\n        else:\n            subprocess.check_call([\"virt-sparsify\", \"--tmp\",\n                                  tmp_dir,\n                                  src,\n                                  dest])\n\n    def sysprep(self, img_location):\n        # type: (str) -> None\n        \"\"\" Run the virt-sysprep tool over the image to prep the log for\n            re-dist. Removes things like logs and user history files\n        \"\"\"\n        sysprep_cmd = (\"virt-sysprep --no-logfile --operations \"\n                       \"defaults,-ssh-userdir,-ssh-hostkeys,-logfiles -a \" +\n                       img_location)\n        subprocess.check_call(sysprep_cmd.split())\n\n    def vagrant_metadata(self, img_location):\n        # type: (str) -> dict\n        \"\"\" Produce dictionary of necessary vagrant key/values \"\"\"\n        json = {}\n\n        info_output = subprocess.check_output([\"qemu-img\", \"info\",\n                                               img_location]).decode('utf-8')\n        json['virtual_size'] = int((re.search(\"virtual size: (?P<size>\\d+)G\",\n                                    info_output)).group(\"size\"))\n\n        json['format'] = (re.search(\"file format: (?P<format>\\w+)\",\n                                    info_output)).group(\"format\")\n        json['provider'] = 'libvirt'\n\n        return json\n\n\ndef main():\n    with open(TARGET_VERSION_FILE, 'r') as f:\n        TARGET_VERSION = f.read().strip()\n\n    # Default to Xenial as base OS.\n    TARGET_PLATFORM = os.environ.get(\"SECUREDROP_TARGET_PLATFORM\", \"xenial\")\n\n    for srv in [\"app-staging\", \"mon-staging\"]:\n\n        for temp_dir in [\"build\", \"tmp\"]:\n            try:\n                ephemeral_path = join(SCENARIO_PATH, \".molecule\",\n                                      temp_dir)\n                EPHEMERAL_DIRS[temp_dir] = ephemeral_path\n\n                os.makedirs(os.path.join(SCENARIO_PATH, \".molecule\", temp_dir))\n            except OSError:\n                pass\n\n        vm = LibVirtPackager(\"{}_{}\".format(SCENARIO_NAME, srv))\n\n        tmp_img_file = join(EPHEMERAL_DIRS[\"tmp\"], \"wip.img\")\n        packaged_img_file = join(EPHEMERAL_DIRS[\"build\"], \"box.img\")\n\n        print(\"Copying VM image store locally\")\n        subprocess.check_output([\"sudo\", \"cp\",\n                                vm.image_store_path(),  # source\n                                tmp_img_file  # dest\n                                 ])\n\n        print(\"Changing file ownership\")\n        subprocess.check_output([\"sudo\", \"chown\", os.environ['USER'],\n                                tmp_img_file])\n\n        # Run a sysprep on it\n        print(\"Run an image sysprep\")\n        vm.sysprep(tmp_img_file)\n\n        print(\"Rebase Image\")\n        vm.image_rebase(tmp_img_file)\n\n        # Sparsify the image file\n        print(\"Run sparsi-fication on the image\")\n        vm.image_sparsify(src=tmp_img_file,\n                          dest=packaged_img_file,\n                          tmp_dir=EPHEMERAL_DIRS['tmp'],\n                          inplace=True)\n\n        # Write out metadata file\n        with open(join(EPHEMERAL_DIRS['build'], 'metadata.json'),\n                  'w') as mdata:\n            json.dump(\n                vm.vagrant_metadata(packaged_img_file),\n                mdata)\n\n        # Copy in appropriate vagrant file to build dir\n        shutil.copyfile(join(BOX_METADATA_DIR, \"Vagrantfile.\"+srv),\n                        join(EPHEMERAL_DIRS['build'], 'Vagrantfile'))\n\n        print(\"Creating tar file\")\n        box_file = join(BOX_BUILD_DIR, \"{}-{}_{}.box\".format(srv, TARGET_PLATFORM, TARGET_VERSION))\n        with tarfile.open(box_file, \"w|gz\") as tar:\n            for boxfile in [\"box.img\", \"Vagrantfile\", \"metadata.json\"]:\n                tar.add(join(EPHEMERAL_DIRS[\"build\"], boxfile),\n                        arcname=boxfile)\n\n        print(\"Box created at {}\".format(box_file))\n\n        print(\"Updating box metadata\")\n        update_box_metadata(srv, box_file, TARGET_PLATFORM, TARGET_VERSION)\n\n        print(\"Clean-up tmp space\")\n        shutil.rmtree(EPHEMERAL_DIRS['tmp'])\n\n\ndef sha256_checksum(filepath):\n    \"\"\"\n    Returns a SHA256 checksum for a given filepath.\n    \"\"\"\n    checksum = hashlib.sha256()\n    with open(filepath, 'rb') as f:\n        # Read by chunks, to avoid slurping the entire file into memory.\n        # Box files range from 500MB to 1.5GB.\n        for block in iter(lambda: f.read(checksum.block_size), b''):\n            checksum.update(block)\n    return checksum.hexdigest()\n\n\ndef update_box_metadata(server_name, box_file, platform, version):\n    \"\"\"\n    Updates the JSON file of Vagrant box metadata, including remote URL,\n    version number, and SHA256 checksum.\n    \"\"\"\n    # Strip off \"staging\" suffix from box names\n    server_name_short = re.sub('\\-staging$', '', server_name)\n    json_file_basename = \"{}_{}_metadata.json\".format(server_name_short, platform)\n    json_file = os.path.join(BOX_METADATA_DIR, json_file_basename)\n\n    # Read in current JSON metadata, so we can append the new info to it.\n    with open(json_file, \"r\") as f:\n        metadata_config = json.loads(f.read())\n\n    base_url = \"https://dev-bin.ops.securedrop.org/vagrant\"\n    box_name = os.path.basename(box_file)\n    box_url = \"{}/{}\".format(base_url, box_name)\n    box_checksum = sha256_checksum(box_file)\n    box_config = dict(\n        name=\"libvirt\",\n        url=box_url,\n        checksum_type=\"sha256\",\n        checksum=box_checksum,\n    )\n    # Creating list of dicts to adhere to JSON format of Vagrant box metadata\n    providers_list = []\n    providers_list.append(box_config)\n    version_config = dict(\n        version=version,\n        providers=providers_list,\n    )\n    box_versions = metadata_config['versions']\n    box_versions.append(version_config)\n    metadata_config['versions'] = box_versions\n\n    # Write out final, modified data. Does not validate for uniqueness,\n    # so repeated runs on the same version will duplicate version info,\n    # which'll likely break the box fetching. Target file is version-controlled,\n    # though, so easy enough to correct in the event of a mistake.\n    with open(json_file, \"w\") as f:\n        f.write(json.dumps(metadata_config, indent=2, sort_keys=True))\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "molecule/vagrant-packager/package.py"}]}