Compare commits
4 Commits
44c7fb5f03
...
0a2a9d5e09
Author | SHA1 | Date | |
---|---|---|---|
|
0a2a9d5e09 | ||
|
5e7c703cca | ||
|
df4a4e3e1d | ||
|
4b0472cfd3 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
*.swp
|
*.swp
|
||||||
Pipfile.lock
|
Pipfile.lock
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
|
*.whl
|
||||||
|
.egg-info
|
||||||
|
test-jars
|
||||||
|
*.egg-info
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
from . import server, repo, config
|
from . import server, repo, config
|
||||||
from mpm.config import Config
|
from mpm.config import Config
|
||||||
import click
|
import click
|
||||||
|
import logging
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.option("--config", type=click.Path(readable=True, writable=False,
|
@click.option("--config", '-c', type=click.Path(readable=True, writable=False,
|
||||||
dir_okay=False))
|
dir_okay=False))
|
||||||
|
@click.option("--verbose", "-v", is_flag=True, default=False)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def mpm(ctx, config):
|
def mpm(ctx, config, verbose):
|
||||||
|
if verbose:
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
ctx.ensure_object(dict)
|
ctx.ensure_object(dict)
|
||||||
ctx.obj['config'] = Config(config)
|
ctx.obj['config'] = Config(config)
|
||||||
pass
|
pass
|
||||||
|
@ -14,6 +14,10 @@ def repo():
|
|||||||
def list_(ctx):
|
def list_(ctx):
|
||||||
for repo in ctx.obj['config'].repositories():
|
for repo in ctx.obj['config'].repositories():
|
||||||
click.echo("{} ({})".format(click.style(repo.name,bold=True), repo.path))
|
click.echo("{} ({})".format(click.style(repo.name,bold=True), repo.path))
|
||||||
|
if not repo.exists():
|
||||||
|
click.echo(click.style("\tPath not found: {}".format(repo.path),
|
||||||
|
fg='red'))
|
||||||
|
continue
|
||||||
rows = []
|
rows = []
|
||||||
currentPlugin = None
|
currentPlugin = None
|
||||||
pluginVersions = []
|
pluginVersions = []
|
||||||
@ -51,12 +55,15 @@ def add(ctx, name, path):
|
|||||||
|
|
||||||
@repo.command('import', help='Import a plugin to a repository')
|
@repo.command('import', help='Import a plugin to a repository')
|
||||||
@click.argument('repo', type=RepoParamType())
|
@click.argument('repo', type=RepoParamType())
|
||||||
@click.argument('paths', type=click.Path(exists=True, readable=True,
|
@click.argument('paths', type=click.Path(exists=True, readable=True), nargs=-1)
|
||||||
path_type=pathlib.Path), nargs=-1)
|
|
||||||
def import_(repo, paths):
|
def import_(repo, paths):
|
||||||
|
if not repo.exists():
|
||||||
|
click.echo("Repository '{}' not found at {}".format(repo.name, repo.path))
|
||||||
|
return
|
||||||
plugins = []
|
plugins = []
|
||||||
searchPaths = list(paths)
|
searchPaths = list(paths)
|
||||||
for path in searchPaths:
|
for path in searchPaths:
|
||||||
|
path = pathlib.Path(path)
|
||||||
try:
|
try:
|
||||||
if path.is_dir():
|
if path.is_dir():
|
||||||
searchPaths += list(path.glob("*"))
|
searchPaths += list(path.glob("*"))
|
||||||
|
@ -17,6 +17,62 @@ def add(ctx, name, path):
|
|||||||
ctx.obj['config'].save()
|
ctx.obj['config'].save()
|
||||||
click.echo("Added server {} in {}".format(name, path))
|
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')
|
@server.command('list', help='List configured servers')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@click.argument('servers', type=ServerParamType(), nargs=-1)
|
@click.argument('servers', type=ServerParamType(), nargs=-1)
|
||||||
@ -33,37 +89,38 @@ def list_(ctx, servers):
|
|||||||
if not pluginPath.exists():
|
if not pluginPath.exists():
|
||||||
click.echo(click.style("\tServer plugin directory does not exist", fg='red'))
|
click.echo(click.style("\tServer plugin directory does not exist", fg='red'))
|
||||||
continue
|
continue
|
||||||
outdatedLinks = []
|
|
||||||
missing = []
|
|
||||||
installed = []
|
installed = []
|
||||||
|
outdated = []
|
||||||
|
missing = []
|
||||||
unmanaged = []
|
unmanaged = []
|
||||||
conflicts = []
|
|
||||||
for state in sorted(server.pluginStates(ctx.obj['config'].repositories())):
|
for state in sorted(server.pluginStates(ctx.obj['config'].repositories())):
|
||||||
if isinstance(state, OutdatedSymlink):
|
if isinstance(state, Outdated):
|
||||||
outdatedLinks.append(state)
|
outdated.append(state)
|
||||||
elif isinstance(state, Installed):
|
elif isinstance(state, Installed):
|
||||||
installed.append(state)
|
installed.append(state)
|
||||||
elif isinstance(state, MissingVersions):
|
elif isinstance(state, MissingVersions):
|
||||||
missing.append(state)
|
missing.append(state)
|
||||||
elif isinstance(state, UnmanagedFile):
|
elif isinstance(state, UnmanagedFile):
|
||||||
unmanaged.append(state)
|
unmanaged.append(state)
|
||||||
elif isinstance(state, SymlinkConflict):
|
|
||||||
conflicts.append(state)
|
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for state in sorted(installed):
|
for state in sorted(installed):
|
||||||
rows.append([
|
rows.append([
|
||||||
state.plugin.name,
|
state.plugin.name,
|
||||||
str(state.plugin.versionSpec),
|
str(state.plugin.versionSpec),
|
||||||
str(state.currentVersion),
|
str(state.currentVersion.version),
|
||||||
str(state.currentVersion),
|
str(state.currentVersion.version),
|
||||||
])
|
])
|
||||||
for state in sorted(outdatedLinks):
|
for state in sorted(outdated):
|
||||||
|
if state.currentVersion is None:
|
||||||
|
curStr = click.style('Missing', fg='red')
|
||||||
|
else:
|
||||||
|
curStr = click.style(str(state.currentVersion.version), fg='yellow')
|
||||||
rows.append([
|
rows.append([
|
||||||
state.plugin.name,
|
state.plugin.name,
|
||||||
click.style(str(state.wantedVersion), fg='green'),
|
click.style(str(state.plugin.versionSpec), fg='green'),
|
||||||
click.style(str(state.currentVersion), fg='yellow'),
|
curStr,
|
||||||
click.style(str(state.plugin.versionSpec), fg='green')
|
click.style(str(state.wantedVersion), fg='green')
|
||||||
])
|
])
|
||||||
for state in sorted(missing):
|
for state in sorted(missing):
|
||||||
rows.append([
|
rows.append([
|
||||||
@ -79,14 +136,6 @@ def list_(ctx, servers):
|
|||||||
click.style('Unmanaged', fg='yellow', bold=True),
|
click.style('Unmanaged', fg='yellow', bold=True),
|
||||||
''
|
''
|
||||||
])
|
])
|
||||||
for state in sorted(conflicts):
|
|
||||||
rows.append([
|
|
||||||
state.plugin.name,
|
|
||||||
click.style(str(state.plugin.versionSpec), fg='yellow'),
|
|
||||||
click.style('Symlink Conflict', fg='red', bold=True),
|
|
||||||
click.style(str(state.plugin.versionSpec), fg='yellow'),
|
|
||||||
])
|
|
||||||
click.echo("\t{}.jar".format(state.plugin.name))
|
|
||||||
|
|
||||||
if len(rows) > 0:
|
if len(rows) > 0:
|
||||||
click.echo(columnar(rows, ['Plugin', 'Wanted', 'Installed',
|
click.echo(columnar(rows, ['Plugin', 'Wanted', 'Installed',
|
||||||
@ -139,35 +188,38 @@ def sync(ctx, servers):
|
|||||||
allServers = ctx.obj['config'].servers()
|
allServers = ctx.obj['config'].servers()
|
||||||
if len(servers) > 0:
|
if len(servers) > 0:
|
||||||
allServers = servers
|
allServers = 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 allServers:
|
for server in allServers:
|
||||||
|
if not server.exists():
|
||||||
|
click.echo("Server '{}' not found at {}".format(server.name,
|
||||||
|
server.path))
|
||||||
|
continue
|
||||||
click.echo('{} ({}):'.format(server.name, server.path))
|
click.echo('{} ({}):'.format(server.name, server.path))
|
||||||
outdatedLinks = []
|
outdated = []
|
||||||
available = []
|
|
||||||
missing = []
|
missing = []
|
||||||
for state in sorted(server.pluginStates(ctx.obj['config'].repositories())):
|
for state in sorted(server.pluginStates(validRepos)):
|
||||||
if isinstance(state, OutdatedSymlink):
|
if isinstance(state, Outdated):
|
||||||
outdatedLinks.append(state)
|
outdated.append(state)
|
||||||
elif isinstance(state, Available):
|
|
||||||
available.append(state)
|
|
||||||
elif isinstance(state, MissingVersions):
|
elif isinstance(state, MissingVersions):
|
||||||
missing.append(state)
|
missing.append(state)
|
||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
|
|
||||||
for state in sorted(outdatedLinks):
|
for state in sorted(outdated):
|
||||||
|
if state.currentVersion is None:
|
||||||
|
curStr = click.style('Missing', fg='red')
|
||||||
|
else:
|
||||||
|
curStr = click.style(str(state.currentVersion.version), fg='yellow')
|
||||||
rows.append([
|
rows.append([
|
||||||
state.plugin.name,
|
state.plugin.name,
|
||||||
click.style(str(state.wantedVersion), fg='green'),
|
click.style(str(state.plugin.versionSpec), fg='green'),
|
||||||
click.style(str(state.currentVersion), fg='yellow'),
|
curStr,
|
||||||
click.style(str(state.plugin.versionSpec), fg='green')
|
click.style(str(state.wantedVersion.version), fg='green')
|
||||||
])
|
|
||||||
|
|
||||||
for state in sorted(available):
|
|
||||||
rows.append([
|
|
||||||
state.plugin.name,
|
|
||||||
click.style(str(state.wantedVersion), fg='green'),
|
|
||||||
click.style('Missing', fg='red'),
|
|
||||||
click.style(str(state.plugin.version), fg='green')
|
|
||||||
])
|
])
|
||||||
|
|
||||||
click.echo("Missing plugins:")
|
click.echo("Missing plugins:")
|
||||||
@ -183,20 +235,15 @@ def sync(ctx, servers):
|
|||||||
click.echo(columnar(rows, ['Plugin', 'Wanted', 'Installed',
|
click.echo(columnar(rows, ['Plugin', 'Wanted', 'Installed',
|
||||||
'Available'], no_borders=True))
|
'Available'], no_borders=True))
|
||||||
|
|
||||||
if len(outdatedLinks) > 0 or len(available) > 0:
|
if len(outdated):
|
||||||
click.echo("Apply changes? [y/N]", nl=False)
|
click.echo("Apply changes? [y/N]", nl=False)
|
||||||
answer = click.getchar().lower()
|
answer = click.getchar().lower()
|
||||||
if answer == "y":
|
if answer == "y":
|
||||||
with click.progressbar(length=len(available)+len(outdatedLinks),
|
with click.progressbar(length=len(outdated),
|
||||||
label='Synchronizing') as bar:
|
label='Synchronizing') as bar:
|
||||||
i = 0
|
i = 0
|
||||||
for state in available:
|
for state in outdated:
|
||||||
server.installVersion(state.plugin)
|
server.syncToVersion(state.wantedVersion)
|
||||||
server.updateSymlinkForPlugin(state.plugin, state.plugin.version)
|
|
||||||
i += 1
|
|
||||||
bar.update(i)
|
|
||||||
for state in outdatedLinks:
|
|
||||||
server.updateSymlinkForPlugin(state.plugin, state.wantedVersion)
|
|
||||||
i += 1
|
i += 1
|
||||||
bar.update(i)
|
bar.update(i)
|
||||||
else:
|
else:
|
||||||
|
@ -60,11 +60,33 @@ class Config():
|
|||||||
'path': str(path)
|
'path': str(path)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def template(self, name):
|
||||||
|
return self.config['templates'].get(name, dict())
|
||||||
|
|
||||||
|
def mergeConfig(self, serverConf):
|
||||||
|
baseConf = serverConf
|
||||||
|
baseConf['plugins'] = []
|
||||||
|
mergedPlugins = {}
|
||||||
|
templates = [self.template(n) for n in serverConf['inherit']]
|
||||||
|
for template in templates:
|
||||||
|
for plugin in template.get('plugins', []):
|
||||||
|
if plugin['name'] not in mergedPlugins:
|
||||||
|
mergedPlugins[plugin['name']] = dict()
|
||||||
|
mergedPlugins[plugin['name']].update(plugin)
|
||||||
|
for plugin in serverConf['plugins']:
|
||||||
|
if plugin['name'] not in mergedPlugins:
|
||||||
|
mergedPlugins[plugin['name']] = dict()
|
||||||
|
mergedPlugins[plugin['name']].update(plugin)
|
||||||
|
for plugin in mergedPlugins:
|
||||||
|
baseConf['plugins'].append(mergedPlugins[plugin])
|
||||||
|
print(baseConf)
|
||||||
|
return baseConf
|
||||||
|
|
||||||
def servers(self):
|
def servers(self):
|
||||||
return [Server(name, c) for (name, c) in self.config['servers'].items()]
|
return [Server(name, self.mergeConfig(c)) for (name, c) in self.config['servers'].items()]
|
||||||
|
|
||||||
def server(self, name):
|
def server(self, name):
|
||||||
return Server(name, self.config['servers'][name])
|
return Server(name, self.mergeConfig(self.config['servers'][name]))
|
||||||
|
|
||||||
def update_server(self, server, config):
|
def update_server(self, server, config):
|
||||||
self.config['servers'][server] = config
|
self.config['servers'][server] = config
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from semantic_version import Spec, Version
|
from semantic_version import SimpleSpec, Version
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@ -7,20 +7,37 @@ version_pattern = re.compile('^(?P<name>.+)-(?P<version>(?:\.?\d+)+).+jar$')
|
|||||||
|
|
||||||
@total_ordering
|
@total_ordering
|
||||||
class Plugin:
|
class Plugin:
|
||||||
def __init__(self, path):
|
def __init__(self, path, forceName=None, forceVersion=None):
|
||||||
self.path = path
|
self.path = path
|
||||||
pluginName = os.path.basename(path)
|
pluginName = os.path.basename(path)
|
||||||
pluginMatches = version_pattern.match(pluginName)
|
|
||||||
|
|
||||||
if pluginMatches is None:
|
if forceVersion is None:
|
||||||
raise ValueError("Cannot derive plugin name from '{}'".format(path))
|
pluginMatches = version_pattern.match(pluginName)
|
||||||
|
|
||||||
self.name = pluginMatches['name']
|
if pluginMatches is None:
|
||||||
|
raise ValueError("Cannot derive plugin name from '{}'".format(path))
|
||||||
|
|
||||||
try:
|
if forceName is None:
|
||||||
self.version = Version.coerce(pluginMatches['version'])
|
self.name = pluginMatches['name']
|
||||||
except ValueError:
|
else:
|
||||||
raise ValueError("Cannot derive semver from '{}'".format(path))
|
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)
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
os.unlink(self.path)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Plugin({}, {} {})".format(self.path, self.name, self.version)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.name == other.name and self.version == other.version
|
return self.name == other.name and self.version == other.version
|
||||||
@ -38,10 +55,13 @@ class PluginSpec:
|
|||||||
def __init__(self, name, versionSpec):
|
def __init__(self, name, versionSpec):
|
||||||
self.name = name
|
self.name = name
|
||||||
try:
|
try:
|
||||||
self.versionSpec = Spec(str(versionSpec))
|
self.versionSpec = SimpleSpec(str(versionSpec))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
|
raise ValueError("Invalid version spec for plugin {}: {}".format(name, versionSpec))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "PluginSpec({}, {})".format(self.name, self.versionSpec)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{} {}".format(self.name, self.versionSpec)
|
return "{} {}".format(self.name, self.versionSpec)
|
||||||
|
|
||||||
@ -77,28 +97,19 @@ class UnmanagedFile(PluginState):
|
|||||||
return self.filename < other.filename
|
return self.filename < other.filename
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class OutdatedSymlink(PluginState):
|
class Outdated(PluginState):
|
||||||
def __init__(self, plugin, currentVersion, wantedVersion):
|
def __init__(self, plugin, currentVersion, wantedVersion):
|
||||||
super().__init__(plugin)
|
super().__init__(plugin)
|
||||||
self.currentVersion = currentVersion
|
self.currentVersion = currentVersion
|
||||||
self.wantedVersion = wantedVersion
|
self.wantedVersion = wantedVersion
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'OutdatedSymlink({})'.format(self.plugin)
|
return 'Outdated({})'.format(self.plugin)
|
||||||
|
|
||||||
class SymlinkConflict(PluginState):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MissingVersions(PluginState):
|
class MissingVersions(PluginState):
|
||||||
pass
|
def __init__(self, plugin, currentVersion):
|
||||||
|
super().__init__(plugin)
|
||||||
class Available(PluginState):
|
self.currentVersion = currentVersion
|
||||||
def __init__(self, repoPlugin, wantedVersion):
|
|
||||||
super().__init__(repoPlugin)
|
|
||||||
self.wantedVersion = wantedVersion
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'Available({})'.format(self.plugin)
|
|
||||||
|
|
||||||
class Installed(PluginState):
|
class Installed(PluginState):
|
||||||
def __init__(self, plugin, currentVersion):
|
def __init__(self, plugin, currentVersion):
|
||||||
|
@ -8,6 +8,12 @@ class Repo:
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.path = config['path']
|
self.path = config['path']
|
||||||
|
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path) and os.path.isdir(self.path)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Repo({})".format(self.path)
|
||||||
|
|
||||||
def importPlugin(self, plugin):
|
def importPlugin(self, plugin):
|
||||||
dest = "{}/{}-{}.jar".format(self.path, plugin.name, plugin.version)
|
dest = "{}/{}-{}.jar".format(self.path, plugin.name, plugin.version)
|
||||||
shutil.copyfile(plugin.path, dest)
|
shutil.copyfile(plugin.path, dest)
|
||||||
@ -36,75 +42,3 @@ class Repo:
|
|||||||
Plugin(fullPath)
|
Plugin(fullPath)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
yield fullPath
|
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
|
|
||||||
|
|
||||||
|
@ -1,6 +1,33 @@
|
|||||||
from mpm.model import *
|
from mpm.model import *
|
||||||
import shutil
|
import shutil
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import logging
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import semantic_version
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logger = logging.getLogger('mpm.server')
|
||||||
|
|
||||||
|
def digest(fd):
|
||||||
|
if sys.version_info.minor >= 11:
|
||||||
|
return hashlib.file_digest(fd, 'sha256')
|
||||||
|
else:
|
||||||
|
hasher = hashlib.sha256()
|
||||||
|
while True:
|
||||||
|
buf = fd.read(1024)
|
||||||
|
if not buf:
|
||||||
|
break
|
||||||
|
hasher.update(buf)
|
||||||
|
return hasher
|
||||||
|
|
||||||
|
class Template:
|
||||||
|
def __init__(self, name, config):
|
||||||
|
self.name = name
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def plugins(self):
|
||||||
|
return [PluginSpec(p['name'], p.get('version', '*')) for p in self.config['plugins']]
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
def __init__(self, name, config):
|
def __init__(self, name, config):
|
||||||
@ -9,6 +36,9 @@ class Server:
|
|||||||
self.path = config['path']
|
self.path = config['path']
|
||||||
self.pluginPath = self.path+'/plugins'
|
self.pluginPath = self.path+'/plugins'
|
||||||
|
|
||||||
|
def exists(self):
|
||||||
|
return os.path.exists(self.path) and os.path.exists(self.pluginPath)
|
||||||
|
|
||||||
def plugins(self):
|
def plugins(self):
|
||||||
return [PluginSpec(p['name'], p.get('version', '*')) for p in self.config['plugins']]
|
return [PluginSpec(p['name'], p.get('version', '*')) for p in self.config['plugins']]
|
||||||
|
|
||||||
@ -18,86 +48,90 @@ class Server:
|
|||||||
raise KeyError("Cannot add plugin multiple times.")
|
raise KeyError("Cannot add plugin multiple times.")
|
||||||
self.config['plugins'].append({'name': pluginSpec.name, 'version': str(pluginSpec.versionSpec)})
|
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):
|
def pluginStates(self, repos):
|
||||||
managedPluginFilenames = []
|
managedPluginFilenames = []
|
||||||
for plugin in self.plugins():
|
for plugin in self.plugins():
|
||||||
|
logger.debug("Checking %r", plugin)
|
||||||
compatibleVersions = []
|
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)):
|
currentVersion = self.currentVersionForPlugin(plugin.name, repos)
|
||||||
yield SymlinkConflict(plugin)
|
logger.debug('Current version: %s', currentVersion)
|
||||||
continue
|
if currentVersion is not None:
|
||||||
|
if plugin.versionSpec.match(currentVersion.version):
|
||||||
|
compatibleVersions.append(currentVersion)
|
||||||
|
managedPluginFilenames.append(os.path.basename(currentVersion.path))
|
||||||
|
|
||||||
for installedVersion in self.versionsForPlugin(plugin.name, repos):
|
for repo in repos:
|
||||||
if installedVersion in plugin.versionSpec:
|
for repoPlugin in repo.plugins():
|
||||||
compatibleVersions.append(installedVersion)
|
if repoPlugin.name == plugin.name:
|
||||||
|
logger.debug("%r -> %r", repoPlugin.version, plugin.versionSpec)
|
||||||
if len(compatibleVersions) == 0:
|
logger.debug(plugin.versionSpec.match(repoPlugin.version))
|
||||||
for repo in repos:
|
if plugin.versionSpec.match(repoPlugin.version):
|
||||||
for repoPlugin in repo.plugins():
|
|
||||||
if repoPlugin.name == plugin.name and repoPlugin.version in plugin.versionSpec:
|
|
||||||
compatibleVersions.append(repoPlugin)
|
compatibleVersions.append(repoPlugin)
|
||||||
if len(compatibleVersions) == 0:
|
if len(compatibleVersions) == 0:
|
||||||
yield MissingVersions(plugin)
|
logger.debug('No compatible versions found for %s', plugin)
|
||||||
else:
|
yield MissingVersions(plugin, currentVersion)
|
||||||
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
|
||||||
yield Available(preferredVersion, plugin.versionSpec)
|
|
||||||
else:
|
else:
|
||||||
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
preferredVersion = list(reversed(sorted(compatibleVersions)))[0]
|
||||||
currentVersion = self.currentVersionForPlugin(plugin.name)
|
logger.debug('Wanted %r, found %r', plugin, preferredVersion)
|
||||||
|
if currentVersion is not None and preferredVersion == currentVersion:
|
||||||
if currentVersion == preferredVersion:
|
logger.debug("Already installed: %r", currentVersion)
|
||||||
yield Installed(plugin, currentVersion)
|
yield Installed(plugin, currentVersion)
|
||||||
else:
|
else:
|
||||||
yield OutdatedSymlink(plugin, currentVersion, preferredVersion)
|
logger.debug("Update available: %r -> %r", plugin, preferredVersion)
|
||||||
|
yield Outdated(plugin, currentVersion, preferredVersion)
|
||||||
|
|
||||||
otherPlugins = os.listdir(self.pluginPath)
|
otherPlugins = os.listdir(self.pluginPath)
|
||||||
for pluginFile in otherPlugins:
|
for pluginFile in otherPlugins:
|
||||||
if os.path.isfile(os.path.join(self.pluginPath, pluginFile)) and pluginFile not in managedPluginFilenames:
|
if os.path.isfile(os.path.join(self.pluginPath, pluginFile)) and pluginFile not in managedPluginFilenames:
|
||||||
|
logger.debug("Unmanaged file: %s", pluginFile)
|
||||||
yield UnmanagedFile(pluginFile)
|
yield UnmanagedFile(pluginFile)
|
||||||
|
|
||||||
def currentVersionForPlugin(self, pluginName):
|
def currentVersionForPlugin(self, pluginName, repos=[]):
|
||||||
pluginSymlink = os.path.join(self.pluginPath, pluginName + '.jar')
|
plugins = os.listdir(self.pluginPath)
|
||||||
if not os.path.lexists(pluginSymlink):
|
for pluginJar in plugins:
|
||||||
return None
|
if pluginJar.startswith(pluginName) and pluginJar.endswith(".jar"):
|
||||||
suffix = '.jar'
|
if pluginJar == pluginName + ".jar":
|
||||||
pluginJar = os.path.basename(os.readlink(pluginSymlink))
|
logger.debug("Running checksum search to find version of %s", pluginJar)
|
||||||
jarVersion = pluginJar[len(pluginName)+1:len(pluginJar)-len(suffix)]
|
pluginPath = os.path.join(self.pluginPath, pluginJar)
|
||||||
try:
|
with open(pluginPath, 'rb') as fd:
|
||||||
pluginSemver = Version.coerce(jarVersion)
|
installedHash = digest(fd)
|
||||||
except ValueError:
|
pluginName = pluginJar.split('.jar')[0]
|
||||||
pluginSemver = jarVersion
|
defaultVersion = semantic_version.Version("0.0.0")
|
||||||
return pluginSemver
|
for repo in repos:
|
||||||
|
for plugin in repo.plugins():
|
||||||
def versionsForPlugin(self, pluginName, repos):
|
if plugin.name == pluginName:
|
||||||
if os.path.exists(os.path.join(self.pluginPath, 'versions')):
|
with open(plugin.path, 'rb') as fd:
|
||||||
plugins = os.listdir(os.path.join(self.pluginPath, 'versions'))
|
foundHash = digest(fd)
|
||||||
for pluginJar in plugins:
|
if foundHash.hexdigest() == installedHash.hexdigest():
|
||||||
if pluginJar.startswith(pluginName):
|
return Plugin(pluginPath,
|
||||||
prefix = pluginName + '-'
|
forceName=pluginName,
|
||||||
suffix = '.jar'
|
forceVersion = plugin.version)
|
||||||
jarVersion = pluginJar[len(prefix):len(pluginJar)-len(suffix)]
|
return Plugin(pluginPath,
|
||||||
|
forceName=pluginName,
|
||||||
|
forceVersion=defaultVersion)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
pluginSemver = Version.coerce(jarVersion)
|
return Plugin(os.path.join(self.pluginPath, pluginJar))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pluginSemver = jarVersion
|
continue
|
||||||
yield pluginSemver
|
|
||||||
|
|
||||||
def updateSymlinkForPlugin(self, plugin, version):
|
def syncToVersion(self, plugin, repos=[]):
|
||||||
pluginFilename = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, version))
|
dest = os.path.join(self.pluginPath, plugin.normalizedFilename())
|
||||||
pluginSymlink = os.path.join(self.pluginPath, plugin.name + '.jar')
|
logger.debug("Syncing %s to %s", dest, plugin)
|
||||||
linkDst = os.path.relpath(pluginFilename, self.pluginPath)
|
current = self.currentVersionForPlugin(plugin.name, repos)
|
||||||
|
if current is not None:
|
||||||
|
logger.debug("Removing current version %r", current)
|
||||||
|
current.unlink()
|
||||||
|
|
||||||
if os.path.lexists(pluginSymlink):
|
|
||||||
os.unlink(pluginSymlink)
|
|
||||||
os.symlink(linkDst, pluginSymlink)
|
|
||||||
|
|
||||||
def installVersion(self, plugin):
|
|
||||||
if not os.path.exists(os.path.join(self.pluginPath, 'versions')):
|
|
||||||
os.path.mkdir(os.path.join(self.pluginPath, 'versions'))
|
|
||||||
dest = os.path.join(self.pluginPath, 'versions/{}-{}.jar'.format(plugin.name, plugin.version))
|
|
||||||
try:
|
try:
|
||||||
os.link(plugin.path, dest)
|
os.link(plugin.path, dest)
|
||||||
|
logger.debug("Plugin synced")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.debug("Hardlink not possible. Copying instead.")
|
||||||
shutil.copyfile(plugin.path, dest)
|
shutil.copyfile(plugin.path, dest)
|
||||||
|
Loading…
Reference in New Issue
Block a user