server: add a command to lock current versions
This commit is contained in:
		@@ -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:")
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
        pluginMatches = version_pattern.match(pluginName)
 | 
			
		||||
 | 
			
		||||
        if pluginMatches is None:
 | 
			
		||||
            raise ValueError("Cannot derive plugin name from '{}'".format(path))
 | 
			
		||||
        if forceVersion is None:
 | 
			
		||||
            pluginMatches = version_pattern.match(pluginName)
 | 
			
		||||
 | 
			
		||||
        self.name = pluginMatches['name']
 | 
			
		||||
            if pluginMatches is None:
 | 
			
		||||
                raise ValueError("Cannot derive plugin name from '{}'".format(path))
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.version = Version.coerce(pluginMatches['version'])
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            raise ValueError("Cannot derive semver 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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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"):
 | 
			
		||||
                try:
 | 
			
		||||
                    return Plugin(os.path.join(self.pluginPath, pluginJar))
 | 
			
		||||
                except ValueError:
 | 
			
		||||
                    continue
 | 
			
		||||
                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()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user