def generate_version_metadata(config, version, channel, device, path, version_detail=""): """ Helper function that will take selected version info and create the .json version file for the corresponding version tarball. """ metadata = {} metadata['generator'] = "version" metadata['version'] = version metadata['version_detail'] = "version=%s" % version metadata['channel.ini'] = {} metadata['channel.ini']['channel'] = channel metadata['channel.ini']['device'] = device metadata['channel.ini']['version'] = str(version) metadata['channel.ini']['version_detail'] = version_detail with open(path.replace(".tar.xz", ".json"), "w+") as fd: fd.write("%s\n" % json.dumps( metadata, sort_keys=True, indent=4, separators=(",", ": "))) gpg.sign_file(config, "image-signing", path.replace(".tar.xz", ".json"))
def _publish_dummy_to_channel(self, device): """Helper function used to publish a dummy image for selected device""" open(os.path.join(self.config.publish_path, "file-1.tar.xz"), "w+").close() with open(os.path.join(self.config.publish_path, "file-1.json"), "w+") as fd: fd.write(json.dumps({'version_detail': "abcd"})) gpg.sign_file(self.config, "image-signing", os.path.join(self.config.publish_path, "file-1.tar.xz")) device.create_image("full", 1234, "abc", ["file-1.tar.xz"], minversion=1233, bootme=True)
def channels_json(config, path, commit=False): """ Context function (to be used with "with") that will open a channels.json file, parse it, validate it and return the decoded version. If commit is True, the file will then be updated (or created) on exit. """ # If the file doesn't exist, just yield an empty dict json_content = {} if os.path.exists(path): with open(path, "r") as fd: content = fd.read() if content: json_content = json.loads(content) # Validation if not isinstance(json_content, dict): raise TypeError("Invalid channels.json, not a dict.") if commit: orig_json_content = copy.deepcopy(json_content) # Yield the decoded value and save on exit try: yield json_content finally: if commit and (orig_json_content != json_content or not os.path.exists(path)): new_path = "%s.new" % path with open(new_path, "w+") as fd: fd.write("%s\n" % json.dumps(json_content, sort_keys=True, indent=4, separators=(",", ": "))) # Move the signature gpg.sign_file(config, "image-signing", new_path) if os.path.exists("%s.asc" % path): os.remove("%s.asc" % path) os.rename("%s.asc" % new_path, "%s.asc" % path) # Move the index if os.path.exists(path): os.remove(path) os.rename(new_path, path)
def test_expiry(self): test_tree = tree.Tree(self.config) test_tree.create_channel("test") test_tree.create_device("test", "test") # Insert a few images image_path = os.path.join(test_tree.path, "test", "test", "image.tar.xz") open(image_path, "w+").close() gpg.sign_file(self.config, "image-signing", image_path) device = test_tree.get_device("test", "test") device.create_image("full", 1, "test", [image_path]) device.create_image("full", 2, "test", [image_path]) device.create_image("delta", 2, "test", [image_path], base=1) device.create_image("full", 3, "test", [image_path]) device.create_image("delta", 3, "test", [image_path], base=2) device.create_image("delta", 3, "test", [image_path], base=1) device.create_image("full", 4, "test", [image_path]) device.create_image("delta", 4, "test", [image_path], base=3) device.create_image("delta", 4, "test", [image_path], base=2) device.create_image("delta", 4, "test", [image_path], base=1) self.assertEqual(len(device.list_images()), 10) device.expire_images(10) self.assertEqual(len(device.list_images()), 10) device.expire_images(3) self.assertEqual(len(device.list_images()), 6) device.expire_images(3) self.assertEqual(len(device.list_images()), 6) device.expire_images(2) self.assertEqual(len(device.list_images()), 3) device.expire_images(1) self.assertEqual(len(device.list_images()), 1) device.expire_images(0) self.assertEqual(len(device.list_images()), 0)
def test_redirect(self): test_tree = tree.Tree(self.config) # Create some channels and aliases test_tree.create_channel("parent") test_tree.create_channel_redirect("redirect", "parent") # Test standard failure cases self.assertRaises(KeyError, test_tree.create_channel_redirect, "redirect", "parent") self.assertRaises(KeyError, test_tree.create_channel_redirect, "redirect1", "parent1") self.assertRaises(KeyError, test_tree.sync_redirects, "parent1") # Publish a basic image test_tree.create_device("parent", "device") # # First file first = os.path.join(self.config.publish_path, "parent/device/full") open(first, "w+").close() gpg.sign_file(self.config, "image-signing", first) # # Second file second = os.path.join(self.config.publish_path, "parent/device/version-1234.tar.xz") tools.generate_version_tarball(self.config, "parent", "test", "1234", second.replace(".xz", "")) tools.xz_compress(second.replace(".xz", "")) os.remove(second.replace(".xz", "")) gpg.sign_file(self.config, "image-signing", second) with open(second.replace(".tar.xz", ".json"), "w+") as fd: metadata = {} metadata['channel.ini'] = {} metadata['channel.ini']['version_detail'] = "test" fd.write(json.dumps(metadata)) gpg.sign_file(self.config, "image-signing", second.replace(".tar.xz", ".json")) # # Adding the entry device = test_tree.get_device("parent", "device") device.create_image( "full", 1234, "abc", ["parent/device/full", "parent/device/version-1234.tar.xz"]) device.set_phased_percentage(1234, 50) # # Sync the redirects test_tree.sync_redirects("parent") # # Get the target target = test_tree.get_device("redirect", "device") # Confirm the fs layout self.assertTrue(not os.path.exists( os.path.join(self.config.publish_path, "redirect"))) self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "parent", "device"))) self.assertEqual(device.list_images(), target.list_images())
def test_phased_percentage(self): test_tree = tree.Tree(self.config) # Create a channel, device and images test_tree.create_channel("test") test_tree.create_device("test", "test") # # some file first = os.path.join(self.config.publish_path, "test/test/full") open(first, "w+").close() gpg.sign_file(self.config, "image-signing", first) # # Adding a first entry device = test_tree.get_device("test", "test") device.create_image("full", 1234, "abc", ["test/test/full"]) device.set_phased_percentage(1234, 20) self.assertEqual(device.get_phased_percentage(1234), 20) # # Adding a second entry device = test_tree.get_device("test", "test") device.create_image("full", 1235, "abc", ["test/test/full"]) self.assertEqual(device.get_phased_percentage(1235), 100) device.set_phased_percentage(1235, 0) device.set_phased_percentage(1235, 100) # Test standard failure cases self.assertRaises(TypeError, device.set_phased_percentage, 1235, "invalid") self.assertRaises(TypeError, device.set_phased_percentage, 1235, 10.5) self.assertRaises(ValueError, device.set_phased_percentage, 1235, 101) self.assertRaises(ValueError, device.set_phased_percentage, 1235, -1) self.assertRaises(IndexError, device.set_phased_percentage, 4242, 50) self.assertRaises(Exception, device.set_phased_percentage, 1234, 50) self.assertRaises(IndexError, device.get_phased_percentage, 4242)
def test_file_lists(self): test_tree = tree.Tree(self.config) test_tree.create_channel("test") test_tree.create_device("test", "test") self.assertEqual(test_tree.list_missing_files(), []) self.assertEqual(test_tree.list_orphaned_files(), []) # Confirm that the gpg directory is ignored if not os.path.exists(os.path.join(test_tree.path, "gpg")): os.mkdir(os.path.join(test_tree.path, "gpg")) self.assertEqual(test_tree.list_orphaned_files(), []) # Confirm that it picks up extra directories os.mkdir(os.path.join(test_tree.path, "invalid")) self.assertEqual(test_tree.list_orphaned_files(), [os.path.join(test_tree.path, "invalid")]) # And that it gets cleared up test_tree.cleanup_tree() self.assertEqual(test_tree.list_orphaned_files(), []) # Confirm that it picks up extra files open(os.path.join(test_tree.path, "invalid"), "w+").close() self.assertEqual(test_tree.list_orphaned_files(), [os.path.join(test_tree.path, "invalid")]) # And that it gets cleared up test_tree.cleanup_tree() self.assertEqual(test_tree.list_orphaned_files(), []) # Test that keyrings aren't considered as orphaned files keyring_path = os.path.join(test_tree.path, "gpg", "device.tar.xz") open(keyring_path, "w+").close() gpg.sign_file(self.config, "image-signing", keyring_path) test_tree.set_device_keyring("test", "test", keyring_path) self.assertEqual(test_tree.list_orphaned_files(), []) # Test that images aren't considered as orphaned files image_path = os.path.join(test_tree.path, "test", "test", "image.tar.xz") open(image_path, "w+").close() gpg.sign_file(self.config, "image-signing", image_path) open(image_path.replace(".tar.xz", ".json"), "w+").close() gpg.sign_file(self.config, "image-signing", image_path.replace(".tar.xz", ".json")) device = test_tree.get_device("test", "test") device.create_image("full", 12345, "test", [image_path]) self.assertEqual(test_tree.list_orphaned_files(), [])
def test_sign_file(self): test_string = "test-string" test_file = os.path.join(self.temp_directory, "test.txt") with open(test_file, "w+") as fd: fd.write(test_string) # Detached armored signature [os.remove(path) for path in glob.glob("%s.*" % test_file)] self.assertTrue(gpg.sign_file(self.config, "image-signing", test_file)) self.assertTrue(os.path.exists("%s.asc" % test_file)) # Detached binary signature [os.remove(path) for path in glob.glob("%s.*" % test_file)] self.assertTrue( gpg.sign_file(self.config, "image-signing", test_file, armor=False)) self.assertTrue(os.path.exists("%s.sig" % test_file)) # Standard armored signature [os.remove(path) for path in glob.glob("%s.*" % test_file)] self.assertTrue( gpg.sign_file(self.config, "image-signing", test_file, detach=False)) self.assertTrue(os.path.exists("%s.asc" % test_file)) # Standard binary signature [os.remove(path) for path in glob.glob("%s.*" % test_file)] self.assertTrue( gpg.sign_file(self.config, "image-signing", test_file, detach=False, armor=False)) self.assertTrue(os.path.exists("%s.gpg" % test_file)) # Failure cases self.assertRaises(Exception, gpg.sign_file, self.config, "invalid", test_file) [os.remove(path) for path in glob.glob("%s.*" % test_file)] gpg.sign_file(self.config, "image-signing", test_file) self.assertRaises(Exception, gpg.sign_file, self.config, "image-signing", test_file) self.assertRaises(Exception, gpg.sign_file, self.config, "image-signing", "invalid")
def index_json(config, path, commit=False): """ Context function (to be used with "with") that will open an index.json file, parse it, validate it and return the decoded version. If commit is True, the file will then be updated (or created) on exit. """ # If the file doesn't exist, just yield an empty dict json_content = {} json_content['global'] = {} json_content['images'] = [] if os.path.exists(path): with open(path, "r") as fd: content = fd.read() if content: json_content = json.loads(content) # Validation if not isinstance(json_content, dict): raise TypeError("Invalid index.json, not a dict.") if commit: orig_json_content = copy.deepcopy(json_content) # Yield the decoded value and save on exit try: yield json_content finally: # Remove any invalid attribute versions = sorted({image['version'] for image in json_content['images']}) if versions: last_version = versions[-1] # Remove phased-percentage from any old image for image in json_content['images']: if image['version'] != last_version and \ "phased-percentage" in image: image.pop("phased-percentage") # Save to disk if commit and (orig_json_content != json_content or not os.path.exists(path)): json_content['global']['generated_at'] = time.strftime( "%a %b %d %H:%M:%S UTC %Y", time.gmtime()) new_path = "%s.new" % path with open(new_path, "w+") as fd: fd.write("%s\n" % json.dumps(json_content, sort_keys=True, indent=4, separators=(",", ": "))) # Move the signature gpg.sign_file(config, "image-signing", new_path) if os.path.exists("%s.asc" % path): os.remove("%s.asc" % path) os.rename("%s.asc" % new_path, "%s.asc" % path) # Move the index if os.path.exists(path): os.remove(path) os.rename(new_path, path)
def sync_alias(self, channel_name): """ Update a channel with data from its parent. """ with channels_json(self.config, self.indexpath) as channels: if channel_name not in channels: raise KeyError("Couldn't find channel: %s" % channel_name) if "alias" not in channels[channel_name] or \ channels[channel_name]['alias'] == channel_name: raise TypeError("Not a channel alias") target_name = channels[channel_name]['alias'] if target_name not in channels: raise KeyError("Couldn't find target channel: %s" % target_name) # Start by looking for added/removed devices devices = set(channels[channel_name]['devices'].keys()) target_devices = set(channels[target_name]['devices'].keys()) # # Remove any removed device for device in devices - target_devices: self.remove_device(channel_name, device) # # Add any missing device for device in target_devices - devices: self.create_device(channel_name, device) # Iterate through all the devices to import builds for device_name in target_devices: device = self.get_device(channel_name, device_name) target_device = self.get_device(target_name, device_name) # Extract all the current builds device_images = {(image['version'], image.get("base", None), image['type']) for image in device.list_images()} target_images = {(image['version'], image.get("base", None), image['type']) for image in target_device.list_images()} # Remove any removed image for image in device_images - target_images: device.remove_image(image[2], image[0], base=image[1]) # Create the path if it doesn't exist if not os.path.exists(device.path): os.makedirs(device.path) # Add any missing image with index_json(self.config, device.indexpath, True) as index: for image in sorted(target_images - device_images): orig = [entry for entry in target_device.list_images() if entry['type'] == image[2] and entry['version'] == image[0] and entry.get("base", None) == image[1]] entry = copy.deepcopy(orig[0]) # Remove the current version tarball version_detail = None version_index = len(entry['files']) for fentry in entry['files']: if fentry['path'].endswith("version-%s.tar.xz" % entry['version']): version_path = "%s/%s" % ( self.config.publish_path, fentry['path']) if os.path.exists( version_path.replace(".tar.xz", ".json")): with open( version_path.replace( ".tar.xz", ".json")) as fd: metadata = json.loads(fd.read()) if "channel.ini" in metadata: version_detail = \ metadata['channel.ini'].get( "version_detail", None) version_index = fentry['order'] entry['files'].remove(fentry) break # Generate a new one path = os.path.join(device.path, "version-%s.tar.xz" % entry['version']) abspath, relpath = tools.expand_path(path, device.pub_path) if not os.path.exists(abspath): tools.generate_version_tarball( self.config, channel_name, device_name, str(entry['version']), abspath.replace(".xz", ""), version_detail=version_detail, channel_target=target_name) tools.xz_compress(abspath.replace(".xz", "")) os.remove(abspath.replace(".xz", "")) gpg.sign_file(self.config, "image-signing", abspath) with open(abspath, "rb") as fd: checksum = sha256(fd.read()).hexdigest() # Generate the new file entry version = {} version['order'] = version_index version['path'] = "/%s" % "/".join( relpath.split(os.sep)) version['signature'] = "/%s.asc" % "/".join( relpath.split(os.sep)) version['checksum'] = checksum version['size'] = int(os.stat(abspath).st_size) # And add it entry['files'].append(version) index['images'].append(entry) # Sync phased-percentage versions = sorted({entry[0] for entry in target_images}) if versions: device.set_phased_percentage( versions[-1], target_device.get_phased_percentage(versions[-1])) return True
def test_index(self): device = tree.Device(self.config, self.temp_directory) # Check without files self.assertRaises(Exception, device.create_image, "full", 1234, "test", []) # Check with missing file self.assertRaises(Exception, device.create_image, "full", 1234, "test", ["file"]) # Check with missing signature open(os.path.join(self.config.publish_path, "file"), "w+").close() self.assertRaises(Exception, device.create_image, "full", 1234, "test", ["file"]) # Check with missing base version gpg.sign_file(self.config, "image-signing", os.path.join(self.config.publish_path, "file")) self.assertRaises(KeyError, device.create_image, "delta", 1234, "test", ["file"]) # Check with extra base version self.assertRaises(KeyError, device.create_image, "full", 1234, "test", ["file"], base=1233) # Check with extra minimum version self.assertRaises(KeyError, device.create_image, "delta", 1234, "test", ["file"], base=1233, minversion=1233) # Valid full image open(os.path.join(self.config.publish_path, "second"), "w+").close() gpg.sign_file(self.config, "image-signing", os.path.join(self.config.publish_path, "second")) device.create_image("full", 1234, "abc", ["file", "second"], minversion=1233, bootme=True) # Valid delta image device.create_image("delta", 1234, "abc", ["file", "second"], base=1233, bootme=True, version_detail="blah") # Check the image list self.assertEqual(device.list_images(), [{ 'bootme': True, 'description': 'abc', 'minversion': 1233, 'type': 'full', 'version': 1234, 'files': [{ 'signature': '/file.asc', 'path': '/file', 'checksum': 'e3b0c44298fc1c149afbf4c8996fb924' '27ae41e4649b934ca495991b7852b855', 'size': 0, 'order': 0 }, { 'signature': '/second.asc', 'path': '/second', 'checksum': 'e3b0c44298fc1c149afbf4c8996fb924' '27ae41e4649b934ca495991b7852b855', 'size': 0, 'order': 1 }] }, { 'bootme': True, 'description': 'abc', 'type': 'delta', 'base': 1233, 'version': 1234, 'version_detail': "blah", 'files': [{ 'signature': '/file.asc', 'path': '/file', 'checksum': 'e3b0c44298fc1c149afbf4c8996fb924' '27ae41e4649b934ca495991b7852b855', 'size': 0, 'order': 0 }, { 'signature': '/second.asc', 'path': '/second', 'checksum': 'e3b0c44298fc1c149afbf4c8996fb924' '27ae41e4649b934ca495991b7852b855', 'size': 0, 'order': 1 }] }]) # Set descriptions device.set_description("delta", 1234, "test", {"fr": "essai"}, 1233) entry = device.get_image("delta", 1234, 1233) self.assertEqual(entry['description'], "test") self.assertEqual(entry['description_fr'], "essai") self.assertRaises(TypeError, device.set_description, "delta", 1234, "test", ['test'], 1233) # Remove the images device.remove_image("delta", 1234, 1233) device.remove_image("full", 1234) self.assertEqual(device.list_images(), []) # Error case of remove_image self.assertRaises(ValueError, device.remove_image, "invalid", 1234) self.assertRaises(ValueError, device.remove_image, "delta", 1234) self.assertRaises(IndexError, device.remove_image, "delta", 1234, 1232) # Test invalid json with open(device.indexpath, "w+") as fd: fd.write("test") self.assertRaises(ValueError, device.list_images) with open(device.indexpath, "w+") as fd: fd.write("[]") self.assertRaises(TypeError, device.list_images)
def test_rename(self): test_tree = tree.Tree(self.config) # Create some channels and aliases test_tree.create_channel("old") test_tree.create_channel("existing") # Test standard failure cases self.assertRaises(KeyError, test_tree.rename_channel, "old1", "test/new") self.assertRaises(KeyError, test_tree.rename_channel, "old", "existing") # Publish a basic image test_tree.create_device("old", "device") # # First file first = os.path.join(self.config.publish_path, "old/device/full") open(first, "w+").close() gpg.sign_file(self.config, "image-signing", first) # # Second file second = os.path.join(self.config.publish_path, "old/device/version-1234.tar.xz") tools.generate_version_tarball(self.config, "old", "test", "1234", second.replace(".xz", "")) tools.xz_compress(second.replace(".xz", "")) os.remove(second.replace(".xz", "")) gpg.sign_file(self.config, "image-signing", second) with open(second.replace(".tar.xz", ".json"), "w+") as fd: metadata = {} metadata['channel.ini'] = {} metadata['channel.ini']['version_detail'] = "test" fd.write(json.dumps(metadata)) gpg.sign_file(self.config, "image-signing", second.replace(".tar.xz", ".json")) # # Adding the entry device = test_tree.get_device("old", "device") device.create_image( "full", 1234, "abc", ["old/device/full", "old/device/version-1234.tar.xz"]) device.set_phased_percentage(1234, 50) # Rename os.makedirs(os.path.join(self.config.publish_path, "test/new")) self.assertRaises(Exception, test_tree.rename_channel, "old", "test/new") os.rmdir(os.path.join(self.config.publish_path, "test/new")) os.rmdir(os.path.join(self.config.publish_path, "test")) self.assertTrue(test_tree.rename_channel("old", "test/new")) self.assertEqual( test_tree.list_channels()['test/new'], {'devices': { 'device': { 'index': '/test/new/device/index.json' } }})
def test_channels(self): # Test getting a tree instance test_tree = tree.Tree(self.config) self.assertEqual(test_tree.list_channels(), {}) # Test publishing a keyring keyring = gpg.Keyring(self.config, "image-signing") keyring.set_metadata("image-signing") keyring.import_keys( os.path.join(self.config.gpg_key_path, "image-signing")) self.assertRaises(Exception, test_tree.publish_keyring, "image-signing") keyring_tar = keyring.generate_tarball() tools.xz_compress(keyring_tar) self.assertRaises(Exception, test_tree.publish_keyring, "image-signing") gpg.sign_file(self.config, "image-master", "%s.xz" % keyring_tar) test_tree.publish_keyring("image-signing") self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "gpg", "image-signing" ".tar.xz"))) self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "gpg", "image-signing" ".tar.xz.asc"))) # Test invalid tree path self.assertRaises(Exception, tree.Tree, self.config, os.path.join(self.temp_directory, "invalid")) # Test channel creation test_tree.create_channel("first") test_tree.create_channel("second") self.assertRaises(KeyError, test_tree.create_channel, "second") # Test channel removal test_tree.remove_channel("first") test_tree.remove_channel("second") self.assertRaises(KeyError, test_tree.remove_channel, "second") # Test invalid json with open(test_tree.indexpath, "w+") as fd: fd.write("[]") self.assertRaises(TypeError, test_tree.list_channels) with open(test_tree.indexpath, "w+") as fd: fd.write("{'a': 'a'}") self.assertRaises(ValueError, test_tree.list_channels) os.remove(test_tree.indexpath) # Test hidding a channel test_tree.create_channel("testing") self.assertEqual(test_tree.list_channels(), {'testing': { 'devices': {} }}) test_tree.hide_channel("testing") self.assertEqual(test_tree.list_channels(), {'testing': { 'devices': {}, 'hidden': True }}) test_tree.show_channel("testing") self.assertEqual(test_tree.list_channels(), {'testing': { 'devices': {} }}) self.assertRaises(KeyError, test_tree.hide_channel, "invalid") self.assertRaises(KeyError, test_tree.show_channel, "invalid") test_tree.remove_channel("testing") # Test device creation test_tree.create_channel("testing") test_tree.create_device("testing", "test") self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "testing", "test", "index.json"))) self.assertEqual( test_tree.list_channels(), { 'testing': { 'devices': { 'test': { 'index': '/testing/test/' 'index.json' } } } }) self.assertRaises(KeyError, test_tree.create_device, "invalid", "test") self.assertRaises(KeyError, test_tree.create_device, "testing", "test") # Test listing devices self.assertRaises(KeyError, test_tree.list_devices, "invalid") self.assertEqual(test_tree.list_devices("testing"), ["test"]) # Test the index generation os.mkdir(os.path.join(self.config.publish_path, "testing", "empty")) self.assertRaises(Exception, test_tree.generate_index) test_tree.generate_index("I know what I'm doing") self.assertEqual( test_tree.list_channels(), { 'testing': { 'devices': { 'test': { 'index': '/testing/test/' 'index.json' } } } }) device_keyring = os.path.join(self.config.publish_path, "testing", "test", "device.tar.xz") open(device_keyring, "w+").close() test_tree.generate_index("I know what I'm doing") self.assertEqual( test_tree.list_channels(), { 'testing': { 'devices': { 'test': { 'index': '/testing/test/' 'index.json' } } } }) gpg.sign_file(self.config, "image-signing", device_keyring) test_tree.generate_index("I know what I'm doing") self.assertEqual( test_tree.list_channels(), { 'testing': { 'devices': { 'test': { 'index': '/testing/test/index.json', 'keyring': { 'path': '/testing/test/' 'device.tar.xz', 'signature': '/testing/test/' 'device.tar' '.xz.asc' } } } } }) # Test grabbing a device entry self.assertRaises(KeyError, test_tree.get_device, "invalid", "test") self.assertRaises(KeyError, test_tree.get_device, "testing", "invalid") self.assertTrue( isinstance(test_tree.get_device("testing", "test"), tree.Device)) # Test device removal test_tree.create_device("testing", "to-remove") self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "testing", "to-remove", "index.json"))) self.assertRaises(KeyError, test_tree.remove_device, "invalid", "test") self.assertRaises(KeyError, test_tree.remove_device, "testing", "invalid") test_tree.remove_device("testing", "to-remove") self.assertFalse( os.path.exists( os.path.join(self.config.publish_path, "testing", "to-remove", "index.json"))) self.assertEqual( test_tree.list_channels(), { 'testing': { 'devices': { 'test': { 'index': '/testing/test/index.json', 'keyring': { 'path': '/testing/test/device' '.tar.xz', 'signature': '/testing/test/devi' 'ce.tar.xz.asc' } } } } }) # Test setting the device keyring self.assertRaises(KeyError, test_tree.set_device_keyring, "invalid", "test", "invalid") self.assertRaises(KeyError, test_tree.set_device_keyring, "testing", "invalid", "invalid") test_tree.set_device_keyring("testing", "test", "testing/test/device.tar.xz") self.assertRaises(Exception, test_tree.set_device_keyring, "testing", "test", "invalid") unsigned_path = os.path.join(self.config.publish_path, "unsigned") open(unsigned_path, "w+").close() self.assertRaises(Exception, test_tree.set_device_keyring, "testing", "test", "unsigned") os.remove(unsigned_path)
def test_per_device_redirect(self): """ """ test_tree = tree.Tree(self.config) # Create some channels test_tree.create_channel("parent") test_tree.create_channel("redirect") # Create the required devices test_tree.create_device("parent", "device") test_tree.create_device("parent", "other") test_tree.create_device("redirect", "other") # Test standard failure cases self.assertRaises(KeyError, test_tree.create_per_device_channel_redirect, "device", "redirect1", "parent") self.assertRaises(KeyError, test_tree.create_per_device_channel_redirect, "other", "redirect", "parent") self.assertRaises(KeyError, test_tree.create_per_device_channel_redirect, "device", "redirect", "parent1") self.assertRaises(KeyError, test_tree.create_per_device_channel_redirect, "device2", "redirect", "parent") # Create the device channel redirect test_tree.create_per_device_channel_redirect("device", "redirect", "parent") # Publish an image # # First file first = os.path.join(self.config.publish_path, "parent/device/full") open(first, "w+").close() gpg.sign_file(self.config, "image-signing", first) # # Second file second = os.path.join(self.config.publish_path, "parent/device/version-1234.tar.xz") tools.generate_version_tarball(self.config, "parent", "test", "1234", second.replace(".xz", "")) tools.xz_compress(second.replace(".xz", "")) os.remove(second.replace(".xz", "")) gpg.sign_file(self.config, "image-signing", second) with open(second.replace(".tar.xz", ".json"), "w+") as fd: metadata = {} metadata['channel.ini'] = {} metadata['channel.ini']['version_detail'] = "test" fd.write(json.dumps(metadata)) gpg.sign_file(self.config, "image-signing", second.replace(".tar.xz", ".json")) # # Adding the entry device = test_tree.get_device("parent", "device") device.create_image( "full", 1234, "abc", ["parent/device/full", "parent/device/version-1234.tar.xz"]) device.set_phased_percentage(1234, 50) # # Get the target target = test_tree.get_device("redirect", "device") # Confirm the fs layout self.assertTrue( os.path.exists(os.path.join(self.config.publish_path, "redirect"))) self.assertFalse( os.path.exists( os.path.join(self.config.publish_path, "redirect", "device"))) self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "parent", "device"))) self.assertEqual(device.list_images(), target.list_images()) # Check the channels index channels = test_tree.list_channels() self.assertIn("other", channels['redirect']['devices']) self.assertEqual(channels['redirect']['devices']['other']['index'], "/redirect/other/index.json") self.assertIn("device", channels['redirect']['devices']) self.assertEqual(channels['redirect']['devices']['device']['index'], "/parent/device/index.json") self.assertIn("redirect", channels['redirect']['devices']['device']) # Try removing a per-channel redirect self.assertTrue(test_tree.remove_device("redirect", "device")) # Confirm files are not removed self.assertTrue( os.path.exists( os.path.join(self.config.publish_path, "parent", "device", "index.json")))
def test_alias(self): test_tree = tree.Tree(self.config) # Create some channels and aliases test_tree.create_channel("parent") test_tree.create_channel_alias("alias", "parent") test_tree.create_channel_redirect("redirect", "alias") # Test standard failure cases self.assertRaises(KeyError, test_tree.create_channel_alias, "alias", "parent") self.assertRaises(KeyError, test_tree.create_channel_alias, "alias1", "parent1") self.assertRaises(KeyError, test_tree.change_channel_alias, "alias1", "parent") self.assertRaises(KeyError, test_tree.change_channel_alias, "alias", "parent1") self.assertRaises(KeyError, test_tree.change_channel_alias, "parent", "parent") self.assertRaises(KeyError, test_tree.change_channel_alias, "redirect", "parent") self.assertRaises(KeyError, test_tree.sync_aliases, "missing") self.assertRaises(KeyError, test_tree.sync_alias, "missing") self.assertRaises(TypeError, test_tree.sync_alias, "parent") test_tree.remove_channel("parent") self.assertRaises(KeyError, test_tree.sync_alias, "alias") test_tree.create_channel("parent") # Publish a basic image test_tree.create_device("parent", "device") test_tree.create_device("parent", "device2") test_tree.create_device("parent", "device3") test_tree.create_device("alias", "device1") # # First file first = os.path.join(self.config.publish_path, "parent/device/full") open(first, "w+").close() gpg.sign_file(self.config, "image-signing", first) # # Second file second = os.path.join(self.config.publish_path, "parent/device/version-1234.tar.xz") tools.generate_version_tarball(self.config, "parent", "test", "1234", second.replace(".xz", "")) tools.xz_compress(second.replace(".xz", "")) os.remove(second.replace(".xz", "")) gpg.sign_file(self.config, "image-signing", second) with open(second.replace(".tar.xz", ".json"), "w+") as fd: metadata = {} metadata['channel.ini'] = {} metadata['channel.ini']['version_detail'] = "test" fd.write(json.dumps(metadata)) gpg.sign_file(self.config, "image-signing", second.replace(".tar.xz", ".json")) # # Adding the entry device = test_tree.get_device("parent", "device") device.create_image( "full", 1234, "abc", ["parent/device/full", "parent/device/version-1234.tar.xz"]) device.set_phased_percentage(1234, 50) # # Adding a fake entry to the alias channel device = test_tree.get_device("alias", "device") device.create_image( "full", 1235, "abc", ["parent/device/full", "parent/device/version-1234.tar.xz"]) # Sync the alises device3 = test_tree.get_device("alias", "device3") shutil.rmtree(device3.path) test_tree.sync_aliases("parent") alias_device = test_tree.get_device("alias", "device") self.assertEqual(alias_device.get_phased_percentage(1234), 50) test_tree.create_channel("new_parent") test_tree.change_channel_alias("alias", "new_parent") test_tree.remove_channel("alias") test_tree.remove_channel("new_parent") test_tree.remove_channel("parent")