rewrite to be more dnf-like, with config editing

This commit is contained in:
Victoria Fierce 2022-09-03 14:43:47 +02:00
parent 81adb51f5b
commit 99f4220a67
7 changed files with 575 additions and 377 deletions

View File

@ -1,6 +1,6 @@
# A tool to manage plugins on play.malloc.gg
# Minecraft Plugin Sync
plugin-sync is a tool used to maintain the various servers and their plugins on
mpm is a tool used to maintain the various servers and their plugins on
play.malloc.gg.
Malloc runs a heterogenous environment of server versions, depending on what
@ -12,7 +12,7 @@ tool to help out the workload.
## Use cases
plugin-sync is highly specialized for the purposes of malloc.gg, but it should
mpm is highly specialized for the purposes of malloc.gg, but it should
be able to work in other systems where:
- You have multiple servers in a network

267
main.py Executable file
View File

@ -0,0 +1,267 @@
#!/bin/env python
import argparse
import pathlib
import sys
import yaml
import os
from repo import Repo
from server import Server
from model import *
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
DEFAULT_CONFIG = """
repositories: {}
servers: {}
"""
class Config():
def __init__(self, path):
if path is None:
path = './mpm.yaml'
self.path = path
with open(path, 'r') as fd:
self.yaml = yaml.load(fd, Loader=Loader)
self.config = yaml.load(DEFAULT_CONFIG, Loader=Loader)
if isinstance(self.yaml, dict):
self.config.update(self.yaml)
def repository(self, name):
return Repo(name, self.config['repositories'][name])
def repositories(self):
return [Repo(name, c) for (name, c) in self.config['repositories'].items()]
def update_repository(self, name, config):
self.config['repositories'][name] = config
def add_repository(self, name, path):
if name in self.config['repositories']:
raise ValueError('Repository already exists')
self.update_repository(name, {
'path': path
})
def servers(self):
return [Server(name, c) for (name, c) in self.config['servers'].items()]
def server(self, name):
return Server(name, self.config['servers'][name])
def update_server(self, server, config):
self.config['servers'][server] = config
def add_server(self, name, path):
if name in self.config['servers']:
raise ValueError("Server already exists")
self.update_server(name, {
'path': path,
'plugins': [],
'inherit': []
})
def save(self):
stream = open(self.path, 'w')
yaml.dump(self.config, stream, Dumper=Dumper)
stream.close()
def do_repo_add(args, config):
if not os.path.exists(args.path):
os.makedirs(args.path)
config.add_repository(args.name, args.path)
config.save()
print("Added repository {}".format(args.path))
def do_repo_list(args, config):
for repo in config.repositories():
print("{} ({})".format(repo.name, repo.path))
for plugin in sorted(repo.plugins()):
print('\t', plugin.name, '\t', plugin.version)
for badFile in sorted(repo.badFiles()):
print('\tWARNING: Unknown file', badFile)
def do_repo_import(args, config):
repo = config.repository(args.name)
plugins = []
for path in args.path:
try:
plugins.append(Plugin(path))
except:
print("Bad plugin filename {}".format(path))
if len(plugins) == 0:
print("No plugins found.")
print('Found the following plugins:')
for plugin in plugins:
print("\t{} {}".format(plugin.name, plugin.version))
print("Import plugins into {}? [y/N]".format(repo.name))
answer = input().lower()
if answer == "y":
for plugin in plugins:
repo.importPlugin(plugin)
print("Imported!")
else:
print("Cancelled.")
def do_server_add(args, config):
config.add_server(args.name, args.path)
config.save()
print("Added server {} in {}".format(args.name, args.path))
def do_server_list(args, config):
for server in config.servers():
print('{} ({}):'.format(server.name, server.path))
outdatedLinks = []
missing = []
installed = []
unmanaged = []
conflicts = []
for state in sorted(server.pluginStates(config.repositories())):
if isinstance(state, OutdatedSymlink):
outdatedLinks.append(state)
elif isinstance(state, Installed):
installed.append(state)
elif isinstance(state, MissingVersions):
missing.append(state)
elif isinstance(state, UnmanagedFile):
unmanaged.append(state)
elif isinstance(state, SymlinkConflict):
conflicts.append(state)
print("Installed plugins:")
for state in sorted(installed):
print("\t{} {}: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion))
print("Oudated symlinks:")
for state in sorted(outdatedLinks):
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
print("Missing plugins:")
for state in sorted(missing):
print("\t{}: {}".format(state.plugin.name, state.plugin.versionSpec))
print("Unmanaged files:")
for state in sorted(unmanaged):
print("\t{}".format(state.filename))
print("Symlink Conflicts:")
for state in sorted(conflicts):
print("\t{}.jar".format(state.plugin.name))
def do_server_add_plugin(args, config):
server = config.server(args.server)
plugins = []
for pluginSpec in args.plugin:
if os.path.exists(pluginSpec):
plugin = Plugin(pluginSpec)
pluginSpec = PluginSpec(plugin.name, str(plugin.version))
else:
allVersions = []
for repo in config.repositories():
allVersions += repo.versionsForPlugin(pluginSpec)
pluginSpec = PluginSpec(pluginSpec, list(reversed(sorted(allVersions)))[0])
plugins.append(pluginSpec)
print("Added {} to {}".format(pluginSpec, server.name))
for pluginSpec in plugins:
print("\t{} {}".format(pluginSpec.name, pluginSpec.versionSpec))
print("Add these plugins to server {}? [y/N]".format(server.name))
answer = input().lower()
if answer == "y":
for pluginSpec in plugins:
server.add_plugin(pluginSpec)
config.update_server(server.name, server.config)
config.save()
print("Added!")
else:
print("Cancelled.")
def do_server_sync(args, config):
for server in config.servers():
print('{} ({}):'.format(server.name, server.path))
outdatedLinks = []
available = []
for state in sorted(server.pluginStates(config.repositories())):
if isinstance(state, OutdatedSymlink):
outdatedLinks.append(state)
elif isinstance(state, Available):
available.append(state)
print("Plugins to update:")
for state in sorted(outdatedLinks):
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
print("New plugins to install:")
for state in sorted(available):
print("\t{}: {}".format(state.plugin.name, state.plugin.version))
if len(outdatedLinks) > 0 or len(available) > 0:
print("Apply changes? [y/N]")
answer = input().lower()
if answer == "y":
for state in available:
server.installVersion(state.plugin)
server.updateSymlinkForPlugin(state.plugin, state.plugin.version)
print("Installed {} {}".format(state.plugin.name, state.plugin.version))
for state in outdatedLinks:
server.updateSymlinkForPlugin(state.plugin, state.wantedVersion)
print("Updated {} to {}".format(state.plugin.name, state.wantedVersion))
else:
print("Not applying changes.")
else:
print("No changes to apply.")
def main():
parser = argparse.ArgumentParser(description='Paper Plugin Sync')
parser.add_argument('--config', dest='config_path', type=Config)
subparsers = parser.add_subparsers()
repos = subparsers.add_parser('repo')
repo_sub = repos.add_subparsers()
repo_add = repo_sub.add_parser('add')
repo_add.add_argument('name', help='Name of the repository')
repo_add.add_argument('path', help='Where to add a repository or create a new one')
repo_add.set_defaults(func=do_repo_add)
repo_list = repo_sub.add_parser('list')
repo_list.set_defaults(func=do_repo_list)
repo_import = repo_sub.add_parser('import')
repo_import.add_argument('name', help='Name of the repository')
repo_import.add_argument('path', nargs="+", help='Path of the plugin to import')
repo_import.set_defaults(func=do_repo_import)
servers = subparsers.add_parser('server')
server_sub = servers.add_subparsers()
server_add = server_sub.add_parser('add')
server_add.add_argument('name', help='Name for the server')
server_add.add_argument('path', help='Path to your server\'s root directory')
server_add.set_defaults(func=do_server_add)
server_list = server_sub.add_parser('list')
server_list.set_defaults(func=do_server_list)
server_add_plugin = server_sub.add_parser('add-plugin')
server_add_plugin.add_argument('server', help='Name of server to modify')
server_add_plugin.add_argument('plugin', nargs='+', help='Plugin file or spec to install')
server_add_plugin.set_defaults(func=do_server_add_plugin)
server_sync = server_sub.add_parser('sync')
server_sync.set_defaults(func=do_server_sync)
args = parser.parse_args()
config = Config(args.config_path)
if 'func' not in args:
parser.print_usage()
else:
args.func(args, config)
if __name__ == "__main__":
main()

99
model.py Normal file
View File

@ -0,0 +1,99 @@
from semantic_version import Spec, Version
from functools import total_ordering
import os
import re
#version_pattern = re.compile('^(?P<name>.*)-(?P<version>[^-]+)(?P<extra>-[^-]+)?\.jar$')
version_pattern = re.compile('^(?P<name>.*)-(?P<version>[^-]+(?:-[^-]+)?)\.jar$')
version_pattern = re.compile('^(?P<name>.+)-(?P<version>(?:\.?\d+)+).+jar$')
@total_ordering
class Plugin:
def __init__(self, path):
self.path = path
pluginName = os.path.basename(path)
pluginMatches = version_pattern.match(pluginName)
if pluginMatches is None:
raise ValueError("Cannot derive plugin name from '{}'".format(path))
self.name = pluginMatches['name']
try:
self.version = Version.coerce(pluginMatches['version'])
except ValueError:
raise ValueError("Cannot derive semver from '{}'".format(path))
def __eq__(self, other):
return self.name == other.name and self.version == other.version
def __ne__(self, other):
return self.name != other.name or self.version != other.version
def __lt__(self, other):
if self.name == other.name:
return self.version < other.version
return self.name < other.name
@total_ordering
class PluginSpec:
def __init__(self, name, versionSpec):
self.name = name
try:
self.versionSpec = Spec(str(versionSpec))
except ValueError:
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
def __str__(self):
return "{} {}".format(self.name, self.versionSpec)
def __eq__(self, other):
return self.name == other.name and self.versionSpec == other.versionSpec
def __ne__(self, other):
return self.name != other.name or self.versionSpec != other.versionSpec
def __lt__(self, other):
return self.name < other.name
@total_ordering
class PluginState:
def __init__(self, plugin):
self.plugin = plugin
def __eq__(self, other):
return self.plugin == other.plugin
def __ne__(self, other):
return self.plugin != other.plugin
def __lt__(self, other):
return self.plugin.name < other.plugin.name
class UnmanagedFile(PluginState):
def __init__(self, filename):
self.filename = filename
def __lt__(self, other):
return self.filename < other.filename
class OutdatedSymlink(PluginState):
def __init__(self, plugin, currentVersion, wantedVersion):
super().__init__(plugin)
self.currentVersion = currentVersion
self.wantedVersion = wantedVersion
class SymlinkConflict(PluginState):
pass
class MissingVersions(PluginState):
pass
class Available(PluginState):
def __init__(self, repoPlugin):
super().__init__(repoPlugin)
class Installed(PluginState):
def __init__(self, plugin, currentVersion):
super().__init__(plugin)
self.currentVersion = currentVersion

View File

@ -1,191 +0,0 @@
#!/bin/env python
import yaml
from semantic_version import Spec, Version
import os
import sys
from functools import total_ordering
try:
from yaml import CLoader as Loader, CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
conf = yaml.load(open('plugins.yml', 'r'), Loader=Loader)
class PluginSpec:
def __init__(self, name, versionSpec):
self.name = name
try:
self.versionSpec = Spec(versionSpec)
except ValueError:
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
@total_ordering
class PluginState:
def __init__(self, plugin):
self.plugin = plugin
def __eq__(self, other):
return self.plugin == other.plugin
def __ne__(self, other):
return self.plugin != other.plugin
def __lt__(self, other):
return self.plugin.name < other.plugin.name
class UnmanagedFile(PluginState):
def __init__(self, filename):
self.filename = filename
def __lt__(self, other):
return self.filename < other.filename
class OutdatedSymlink(PluginState):
def __init__(self, plugin, currentVersion, wantedVersion):
super().__init__(plugin)
self.currentVersion = currentVersion
self.wantedVersion = wantedVersion
class SymlinkConflict(PluginState):
pass
class MissingVersions(PluginState):
pass
class Installed(PluginState):
def __init__(self, plugin, currentVersion):
super().__init__(plugin)
self.currentVersion = currentVersion
class Repo:
def __init__(self, path, pluginSet):
self.path = path
self.pluginSet = pluginSet
def updateSymlinkForPlugin(self, plugin, version):
pluginFilename = os.path.join(self.path, 'versions/{}-{}.jar'.format(plugin.name, version))
pluginSymlink = os.path.join(self.path, plugin.name + '.jar')
linkDst = os.path.relpath(pluginFilename, self.path)
if os.path.lexists(pluginSymlink):
os.unlink(pluginSymlink)
os.symlink(linkDst, pluginSymlink)
def pluginStates(self):
managedPluginFilenames = []
for plugin in self.pluginSet:
compatibleVersions = []
pluginLinkName = '{}.jar'.format(plugin.name)
managedPluginFilenames.append(pluginLinkName)
if os.path.exists(os.path.join(self.path, pluginLinkName)) and not os.path.islink(os.path.join(self.path, pluginLinkName)):
yield SymlinkConflict(plugin)
continue
for installedVersion in self.versionsForPlugin(plugin.name):
if installedVersion in plugin.versionSpec:
compatibleVersions.append(installedVersion)
if len(compatibleVersions) == 0:
yield MissingVersions(plugin)
else:
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
currentVersion = self.currentVersionForPlugin(plugin.name)
if currentVersion == preferredVersion:
yield Installed(plugin, currentVersion)
else:
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
otherPlugins = os.listdir(self.path)
for pluginFile in otherPlugins:
if os.path.isfile(os.path.join(self.path, pluginFile)) and pluginFile not in managedPluginFilenames:
yield UnmanagedFile(pluginFile)
def currentVersionForPlugin(self, pluginName):
pluginSymlink = os.path.join(self.path, pluginName + '.jar')
if not os.path.lexists(pluginSymlink):
return None
suffix = '.jar'
pluginJar = os.path.basename(os.readlink(pluginSymlink))
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
return pluginSemver
def versionsForPlugin(self, pluginName):
plugins = os.listdir(os.path.join(self.path, 'versions'))
for pluginJar in plugins:
if pluginJar.startswith(pluginName):
prefix = pluginName + '-'
suffix = '.jar'
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
yield pluginSemver
for (serverName,server) in conf['servers'].items():
changeset = []
if len(sys.argv) > 1 and serverName not in sys.argv[1:]:
continue
if 'pluginPath' not in server:
continue
if not os.path.exists(server['pluginPath']):
print("Missing plugin path for {}: {}".format(serverName, server['pluginPath']))
else:
print("=== Updating server {}".format(serverName))
pluginSpecs = {}
for inherited in server.get('inherit', ()):
for inheritedPlugin in conf['servers'][inherited]['plugins']:
pluginSpecs[inheritedPlugin['name']] = PluginSpec(inheritedPlugin['name'], str(inheritedPlugin.get('version', '*')))
for pluginConf in server.get('plugins', ()):
pluginSpecs[pluginConf['name']] = PluginSpec(pluginConf['name'], str(pluginConf.get('version', '*')))
repo = Repo(server['pluginPath'], pluginSpecs.values())
outdatedLinks = []
missing = []
installed = []
unmanaged = []
conflicts = []
for state in repo.pluginStates():
if isinstance(state, OutdatedSymlink):
outdatedLinks.append(state)
elif isinstance(state, Installed):
installed.append(state)
elif isinstance(state, MissingVersions):
missing.append(state)
elif isinstance(state, UnmanagedFile):
unmanaged.append(state)
elif isinstance(state, SymlinkConflict):
conflicts.append(state)
print("Installed plugins:")
for state in sorted(installed):
print("\t{} {}: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion))
print("Oudated symlinks:")
for state in sorted(outdatedLinks):
print("\t{} {}: Current: {} Wanted: {}".format(state.plugin.name, state.plugin.versionSpec, state.currentVersion, state.wantedVersion))
print("Missing plugins:")
for state in sorted(missing):
print("\t{}: {}".format(state.plugin.name, state.plugin.versionSpec))
print("Unmanaged files:")
for state in sorted(unmanaged):
print("\t{}".format(state.filename))
print("Symlink Conflicts:")
for state in sorted(conflicts):
print("\t{}.jar".format(state.plugin.name))
if len(outdatedLinks) > 0:
print("Apply changes? [y/N]")
answer = input().lower()
if answer == "y":
for state in outdatedLinks:
repo.updateSymlinkForPlugin(state.plugin, state.wantedVersion)
else:
print("Not applying changes.")

View File

@ -1,183 +0,0 @@
servers:
1.17-smp:
plugins:
- name: Pl3xMap
- name: pl3xmap-signs
1.18-common:
plugins:
- name: voicechat-bukkit-1.18.2
version: 2.2.28
1.17-common:
plugins:
- name: voicechat-bukkit-1.17.1
version: 2.2.28
smp-common:
plugins:
- name: ActionHealth
creative:
inherit:
- common
- 1.17-common
pluginPath: /srv/minecraft/creative/plugins/
plugins:
- name: HolographicDisplays
- name: HolographicExtension
- name: EssentialsXSpawn
- name: WorldSystem
version: 2.4.12
villager-defense:
inherit:
- common
- 1.17-common
pluginPath: /srv/minecraft/villager-defense/plugins/
mcdev:
inherit:
- common
- smp-common
- 1.18-common
- SMP
pluginPath: /srv/minecraft/mcdev/plugins/
plugins:
- name: tabtps-spigot
version: '*'
- name: item-nbt-api-plugin
version: 2.9.2
- name: QuickShop
version: 5
- name: AngelChest
version: '*'
- name: GameModeInventories
version: '*'
SMP:
inherit:
- common
- smp-common
- 1.17-common
- 1.17-smp
pluginPath: /srv/minecraft/server/plugins/
plugins:
- name: ATMSigns
- name: Harbor
- name: BetterRTP
- name: CoreProtect
version: 19.5.*
- name: Chunky
version: 1.2.86
- name: ChunkyBorder
version: 1.0.38
- name: GameModeInventories
version: 3.3.3
- name: HolographicDisplays
version: 2
- name: Regions
version: '0.3'
- name: Shopkeepers
version: 2.13.2
- name: Lands
version: 5.11.18
- name: OpenInv
version: 4.1.8
- name: AngelChest
version: 4.2.0
- name: PvPManager
version: 3
- name: EssentialsXSpawn
version: 2
- name: MythicMobs
version: 5.0.0-alpha1
- name: Model-Engine
version: 2.*
- name: LibsDisguises
version: 10
- name: QuickShop
version: 4.0
- name: ProtocolLib
version: 4.8.0
skyblock:
inherit:
- 1.18-common
- common
pluginPath: /srv/minecraft/skyblock/plugins/
plugins:
- name: BentoBox
version: 1.19.0
lobby:
pluginPath: /srv/minecraft/lobby/plugins/
inherit:
- common
plugins:
- name: ItemJoin
- name: Parkour
version: 6.7.1
- name: GadgetsMenu
version: 5.0.2
- name: Denizen
version: 1.2.1-b1743-REL
- name: JumpPads
version: 1.25.6
- name: VoidSpawn
version: 1.0.1
- name: EssentialsXSpawn
version: 2.18.2
- name: HolographicExtension
version: 1.10.9
- name: HolographicDisplays
version: 2.4.9
- name: Citizens
version: 2.0.28-5
- name: voicechat-bukkit-1.17.1
version: 2.2.28
common:
plugins:
- name: Advanced-Portals
version: 0.7.1
- name: DeluxeMenus
version: 1.13.3
- name: AsyncWorldEdit
version: 3.9.1
- name: CoordinatesHUD
- name: BungeeTabListPlus_BukkitBridge
version: ~3.4
- name: BuycraftX
- name: DBVerifier
version: 1.6.5-malloc
- name: DiscordSRV
version: 1.24.0
- name: EssentialsX
version: 2.18.2
- name: GSit
version: 6.0.2
- name: item-nbt-api-plugin
version: 2.*
- name: LuckPerm-Bukkit
version: 5.4.9
- name: PlaceholderAPI
version: 2.10.9
- name: Plan
version: 5.4.0-build-1354
- name: PlayerPoints
version: 3.0.3
- name: ProtocolLib
version: 4.8.0
- name: SuperVanish
version: 6.2.6
- name: tabtps-spigot
version: 1.3.8
- name: Umfrage
version: 1.6.3-STABLE
- name: Vault
- name: VentureChat
version: 3.3.2
- name: floodgate-bukkit
version: 53
- name: DisableJoinMessage
- name: worldedit-bukkit
- name: worldguard-bukkit
- name: SkQuery
version: 4.1.4
- name: Skript
version: 2.6.0-beta2
- name: skUtilities
version: 0.9.2
- name: Skungee
- name: NametagEdit

110
repo.py Normal file
View File

@ -0,0 +1,110 @@
import os
from model import *
import shutil
class Repo:
def __init__(self, name, config):
self.name = name
self.config = config
self.path = config['path']
def importPlugin(self, plugin):
dest = "{}/{}-{}.jar".format(self.path, plugin.name, plugin.version)
shutil.copyfile(plugin.path, dest)
def plugins(self):
plugins = os.listdir(self.path)
for pluginFile in plugins:
fullPath = os.path.join(self.path, pluginFile)
if os.path.isfile(fullPath):
try:
yield Plugin(fullPath)
except ValueError:
continue
def versionsForPlugin(self, name):
for plugin in self.plugins():
if plugin.name == name:
yield plugin.version
def badFiles(self):
plugins = os.listdir(self.path)
for pluginFile in plugins:
fullPath = os.path.join(self.path, pluginFile)
if os.path.isfile(fullPath):
try:
Plugin(fullPath)
except ValueError:
yield fullPath
class Server:
def __init__(self, path, pluginSet):
self.path = path
self.pluginSet = pluginSet
def updateSymlinkForPlugin(self, plugin, version):
pluginFilename = os.path.join(self.path, 'plugins/versions/{}-{}.jar'.format(plugin.name, version))
pluginSymlink = os.path.join(self.path, plugin.name + '.jar')
linkDst = os.path.relpath(pluginFilename, self.path)
if os.path.lexists(pluginSymlink):
os.unlink(pluginSymlink)
os.symlink(linkDst, pluginSymlink)
def pluginStates(self):
managedPluginFilenames = []
for plugin in self.pluginSet:
compatibleVersions = []
pluginLinkName = '{}.jar'.format(plugin.name)
managedPluginFilenames.append(pluginLinkName)
if os.path.exists(os.path.join(self.path, pluginLinkName)) and not os.path.islink(os.path.join(self.path, pluginLinkName)):
yield SymlinkConflict(plugin)
continue
for installedVersion in self.versionsForPlugin(plugin.name):
if installedVersion in plugin.versionSpec:
compatibleVersions.append(installedVersion)
if len(compatibleVersions) == 0:
yield MissingVersions(plugin)
else:
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
currentVersion = self.currentVersionForPlugin(plugin.name)
if currentVersion == preferredVersion:
yield Installed(plugin, currentVersion)
else:
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
otherPlugins = os.listdir(self.path)
for pluginFile in otherPlugins:
if os.path.isfile(os.path.join(self.path, pluginFile)) and pluginFile not in managedPluginFilenames:
yield UnmanagedFile(pluginFile)
def currentVersionForPlugin(self, pluginName):
pluginSymlink = os.path.join(self.path, pluginName + '.jar')
if not os.path.lexists(pluginSymlink):
return None
suffix = '.jar'
pluginJar = os.path.basename(os.readlink(pluginSymlink))
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
return pluginSemver
def versionsForPlugin(self, pluginName):
plugins = os.listdir(os.path.join(self.path, 'plugins', 'versions'))
for pluginJar in plugins:
if pluginJar.startswith(pluginName):
prefix = pluginName + '-'
suffix = '.jar'
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
yield pluginSemver

96
server.py Normal file
View File

@ -0,0 +1,96 @@
from model import *
import shutil
class Server:
def __init__(self, name, config):
self.name = name
self.config = config
self.path = config['path']
self.pluginPath = self.path+'/plugins'
def plugins(self):
return [PluginSpec(p['name'], p['version']) for p in self.config['plugins']]
def add_plugin(self, pluginSpec):
for plugin in self.config['plugins']:
if plugin['name'] == pluginSpec.name:
raise KeyError("Cannot add plugin multiple times.")
self.config['plugins'].append({'name': pluginSpec.name, 'version': str(pluginSpec.versionSpec)})
def pluginStates(self, repos):
managedPluginFilenames = []
for plugin in self.plugins():
compatibleVersions = []
pluginLinkName = '{}.jar'.format(plugin.name)
managedPluginFilenames.append(pluginLinkName)
if os.path.exists(os.path.join(self.pluginPath, pluginLinkName)) and not os.path.islink(os.path.join(self.pluginPath, pluginLinkName)):
yield SymlinkConflict(plugin)
continue
for installedVersion in self.versionsForPlugin(plugin.name, repos):
if installedVersion in plugin.versionSpec:
compatibleVersions.append(installedVersion)
if len(compatibleVersions) == 0:
for repo in repos:
for repoPlugin in repo.plugins():
if repoPlugin.name == plugin.name and repoPlugin.version in plugin.versionSpec:
compatibleVersions.append(repoPlugin)
if len(compatibleVersions) == 0:
yield MissingVersions(plugin)
else:
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
yield Available(preferredVersion)
else:
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
currentVersion = self.currentVersionForPlugin(plugin.name)
if currentVersion == preferredVersion:
yield Installed(plugin, currentVersion)
else:
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
otherPlugins = os.listdir(self.pluginPath)
for pluginFile in otherPlugins:
if os.path.isfile(os.path.join(self.pluginPath, pluginFile)) and pluginFile not in managedPluginFilenames:
yield UnmanagedFile(pluginFile)
def currentVersionForPlugin(self, pluginName):
pluginSymlink = os.path.join(self.pluginPath, pluginName + '.jar')
if not os.path.lexists(pluginSymlink):
return None
suffix = '.jar'
pluginJar = os.path.basename(os.readlink(pluginSymlink))
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
return pluginSemver
def versionsForPlugin(self, pluginName, repos):
plugins = os.listdir(os.path.join(self.pluginPath, 'versions'))
for pluginJar in plugins:
if pluginJar.startswith(pluginName):
prefix = pluginName + '-'
suffix = '.jar'
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
try:
pluginSemver = Version.coerce(jarVersion)
except ValueError:
pluginSemver = jarVersion
yield pluginSemver
def updateSymlinkForPlugin(self, plugin, version):
pluginFilename = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, version))
pluginSymlink = os.path.join(self.pluginPath, plugin.name + '.jar')
linkDst = os.path.relpath(pluginFilename, self.pluginPath)
if os.path.lexists(pluginSymlink):
os.unlink(pluginSymlink)
os.symlink(linkDst, pluginSymlink)
def installVersion(self, plugin):
dest = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, plugin.version))
shutil.copyfile(plugin.path, dest)