server: add a command to lock current versions

This commit is contained in:
Torrie Fischer 2023-08-20 10:24:26 +02:00
parent df4a4e3e1d
commit 5e7c703cca
3 changed files with 116 additions and 35 deletions

View File

@ -17,6 +17,62 @@ def add(ctx, name, path):
ctx.obj['config'].save()
click.echo("Added server {} in {}".format(name, path))
@server.command(help="Save a server's current plugin list to mpm.yaml")
@click.pass_context
@click.argument('servers', type=ServerParamType(), nargs=-1)
def lock(ctx, servers):
config = ctx.obj['config']
if len(servers) == 0:
servers = config.servers()
validRepos = []
for repo in ctx.obj['config'].repositories():
if not repo.exists():
click.echo("Missing repository: '{}' not found at {}".format(repo.name, repo.path))
else:
validRepos.append(repo)
for server in servers:
click.echo('{} ({}):'.format(server.name, server.path))
savedPlugins = []
changedPlugins = []
for state in server.pluginStates(validRepos):
if isinstance(state, Installed):
savedPlugins.append(state)
elif isinstance(state, Outdated):
changedPlugins.append(state)
elif isinstance(state, MissingVersions):
changedPlugins.append(state)
rows = []
for state in savedPlugins:
newSpec = PluginSpec(state.plugin.name,
state.currentVersion.version)
if newSpec == state.plugin:
continue
rows.append([
state.plugin.name,
click.style('{} -> {}'.format(state.plugin.versionSpec,
state.currentVersion.version), fg='green'),
state.currentVersion.version
])
server.set_plugin_version(state.plugin.name, state.currentVersion.version)
for state in changedPlugins:
if state.currentVersion is not None:
rows.append([
click.style(state.plugin.name, fg='yellow'),
click.style('{} -> {}'.format(state.plugin.versionSpec,
state.currentVersion.version),
fg='yellow'),
state.currentVersion.version
])
server.set_plugin_version(state.plugin.name, state.currentVersion.version)
if len(rows) > 0:
click.echo(columnar(rows, ['Plugin', 'Wanted', 'Installed'],
no_borders=True))
config.update_server(server.name, server.config)
click.echo("Apply changes? [y/N]", nl=False)
answer = click.getchar().lower()
if answer == "y":
config.save()
@server.command('list', help='List configured servers')
@click.pass_context
@click.argument('servers', type=ServerParamType(), nargs=-1)
@ -62,9 +118,9 @@ def list_(ctx, servers):
curStr = click.style(str(state.currentVersion.version), fg='yellow')
rows.append([
state.plugin.name,
click.style(str(state.wantedVersion), fg='green'),
click.style(str(state.plugin.versionSpec), fg='green'),
curStr,
click.style(str(state.plugin.version), fg='green')
click.style(str(state.wantedVersion), fg='green')
])
for state in sorted(missing):
rows.append([
@ -161,9 +217,9 @@ def sync(ctx, servers):
curStr = click.style(str(state.currentVersion.version), fg='yellow')
rows.append([
state.plugin.name,
click.style(str(state.wantedVersion), fg='green'),
click.style(str(state.plugin.versionSpec), fg='green'),
curStr,
click.style(str(state.plugin.version), fg='green')
click.style(str(state.wantedVersion.version), fg='green')
])
click.echo("Missing plugins:")

View File

@ -7,20 +7,28 @@ version_pattern = re.compile('^(?P<name>.+)-(?P<version>(?:\.?\d+)+).+jar$')
@total_ordering
class Plugin:
def __init__(self, path):
def __init__(self, path, forceName=None, forceVersion=None):
self.path = path
pluginName = os.path.basename(path)
if forceVersion is None:
pluginMatches = version_pattern.match(pluginName)
if pluginMatches is None:
raise ValueError("Cannot derive plugin name from '{}'".format(path))
if forceName is None:
self.name = pluginMatches['name']
else:
self.name = forceName
try:
self.version = Version.coerce(pluginMatches['version'])
except ValueError:
raise ValueError("Cannot derive semver from '{}'".format(path))
else:
self.version = forceVersion
self.name = forceName
def normalizedFilename(self):
return "{}-{}.jar".format(self.name, self.version)
@ -98,19 +106,10 @@ class Outdated(PluginState):
def __str__(self):
return 'Outdated({})'.format(self.plugin)
class SymlinkConflict(PluginState):
pass
class MissingVersions(PluginState):
pass
class Available(PluginState):
def __init__(self, repoPlugin, wantedVersion):
super().__init__(repoPlugin)
self.wantedVersion = wantedVersion
def __str__(self):
return 'Available({})'.format(self.plugin)
def __init__(self, plugin, currentVersion):
super().__init__(plugin)
self.currentVersion = currentVersion
class Installed(PluginState):
def __init__(self, plugin, currentVersion):

View File

@ -2,6 +2,7 @@ from mpm.model import *
import shutil
import pathlib
import logging
import hashlib
import os
import semantic_version
@ -26,13 +27,18 @@ class Server:
raise KeyError("Cannot add plugin multiple times.")
self.config['plugins'].append({'name': pluginSpec.name, 'version': str(pluginSpec.versionSpec)})
def set_plugin_version(self, pluginName, versionSpec):
for plugin in self.config['plugins']:
if plugin['name'] == pluginName:
plugin['version'] = str(versionSpec)
def pluginStates(self, repos):
managedPluginFilenames = []
for plugin in self.plugins():
logger.debug("Checking %r", plugin)
compatibleVersions = []
currentVersion = self.currentVersionForPlugin(plugin.name)
currentVersion = self.currentVersionForPlugin(plugin.name, repos)
logger.debug('Current version: %s', currentVersion)
if currentVersion is not None:
if plugin.versionSpec.match(currentVersion.version):
@ -48,7 +54,7 @@ class Server:
compatibleVersions.append(repoPlugin)
if len(compatibleVersions) == 0:
logger.debug('No compatible versions found for %s', plugin)
yield MissingVersions(plugin)
yield MissingVersions(plugin, currentVersion)
else:
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
logger.debug('Wanted %r, found %r', plugin, preferredVersion)
@ -57,7 +63,7 @@ class Server:
yield Installed(plugin, currentVersion)
else:
logger.debug("Update available: %r -> %r", plugin, preferredVersion)
yield Outdated(preferredVersion, currentVersion, plugin.versionSpec)
yield Outdated(plugin, currentVersion, preferredVersion)
otherPlugins = os.listdir(self.pluginPath)
for pluginFile in otherPlugins:
@ -65,19 +71,39 @@ class Server:
logger.debug("Unmanaged file: %s", pluginFile)
yield UnmanagedFile(pluginFile)
def currentVersionForPlugin(self, pluginName):
def currentVersionForPlugin(self, pluginName, repos=[]):
plugins = os.listdir(self.pluginPath)
for pluginJar in plugins:
if pluginJar.startswith(pluginName) and pluginJar.endswith(".jar"):
if pluginJar == pluginName + ".jar":
logger.debug("Running checksum search to find version of %s", pluginJar)
pluginPath = os.path.join(self.pluginPath, pluginJar)
with open(pluginPath, 'rb') as fd:
installedHash = hashlib.file_digest(fd, 'sha256')
pluginName = pluginJar.split('.jar')[0]
defaultVersion = semantic_version.Version("0.0.0")
for repo in repos:
for plugin in repo.plugins():
if plugin.name == pluginName:
with open(plugin.path, 'rb') as fd:
foundHash = hashlib.file_digest(fd, 'sha256')
if foundHash.hexdigest() == installedHash.hexdigest():
return Plugin(pluginPath,
forceName=pluginName,
forceVersion = plugin.version)
return Plugin(pluginPath,
forceName=pluginName,
forceVersion=defaultVersion)
else:
try:
return Plugin(os.path.join(self.pluginPath, pluginJar))
except ValueError:
continue
def syncToVersion(self, plugin):
def syncToVersion(self, plugin, repos=[]):
dest = os.path.join(self.pluginPath, plugin.normalizedFilename())
logger.debug("Syncing %s to %s", dest, plugin)
current = self.currentVersionForPlugin(plugin.name)
current = self.currentVersionForPlugin(plugin.name, repos)
if current is not None:
logger.debug("Removing current version %r", current)
current.unlink()