Drop plugin network install (#2322)

This commit is contained in:
Naoki Takezoe
2019-06-02 13:08:52 +09:00
committed by GitHub
parent 71248cd9b7
commit 6cf00c5c66
9 changed files with 22 additions and 305 deletions

View File

@@ -2,14 +2,11 @@ package gitbucket.core.controller
import java.io.FileInputStream import java.io.FileInputStream
import com.github.zafarkhaja.semver.{Version => Semver}
import gitbucket.core.GitBucketCoreModule
import gitbucket.core.admin.html import gitbucket.core.admin.html
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository} import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.SystemSettingsService._ import gitbucket.core.service.SystemSettingsService._
import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.ssh.SshServer import gitbucket.core.ssh.SshServer
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
@@ -21,7 +18,6 @@ import org.scalatra._
import org.scalatra.forms._ import org.scalatra.forms._
import org.scalatra.i18n.Messages import org.scalatra.i18n.Messages
import scala.collection.JavaConverters._
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
class SystemSettingsController class SystemSettingsController
@@ -93,17 +89,17 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
)(OIDC.apply) )(OIDC.apply)
), ),
"skinName" -> trim(label("AdminLTE skin name", text(required))), "skinName" -> trim(label("AdminLTE skin name", text(required))),
"showMailAddress" -> trim(label("Show mail address", boolean())), "showMailAddress" -> trim(label("Show mail address", boolean())) //,
"pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())), // "pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())),
"proxy" -> optionalIfNotChecked( // "proxy" -> optionalIfNotChecked(
"useProxy", // "useProxy",
mapping( // mapping(
"host" -> trim(label("Proxy host", text(required))), // "host" -> trim(label("Proxy host", text(required))),
"port" -> trim(label("Proxy port", number())), // "port" -> trim(label("Proxy port", number())),
"user" -> trim(label("Keystore", optional(text()))), // "user" -> trim(label("Keystore", optional(text()))),
"password" -> trim(label("Keystore", optional(text()))) // "password" -> trim(label("Keystore", optional(text())))
)(Proxy.apply) // )(Proxy.apply)
) // )
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>
Vector( Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) { if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
@@ -332,58 +328,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}) })
get("/admin/plugins")(adminOnly { get("/admin/plugins")(adminOnly {
// Installed plugins html.plugins(PluginRegistry().getPlugins(), flash.get("info"))
val enabledPlugins = PluginRegistry().getPlugins()
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
val gitbucketSemver = Semver.valueOf(gitbucketVersion)
// Plugins in the remote repository
val repositoryPlugins = if (context.settings.pluginNetworkInstall) {
PluginRepository
.getPlugins()
.map {
meta =>
(meta, meta.versions.reverse.find {
version =>
val semver = Semver.valueOf(version.version)
gitbucketVersion == version.gitbucketVersion && !enabledPlugins.exists { plugin =>
if (plugin.pluginId == meta.id) {
Semver.valueOf(plugin.pluginVersion) match {
case x if x.greaterThan(semver) => true
case x if x.equals(semver) =>
plugin.gitbucketVersion match {
case None => true
case Some(x) => Semver.valueOf(x).greaterThanOrEqualTo(gitbucketSemver)
}
case _ => false
}
} else false
}
})
}
.collect {
case (meta, Some(version)) =>
new PluginInfoBase(
pluginId = meta.id,
pluginName = meta.name,
pluginVersion = version.version,
gitbucketVersion = Some(version.gitbucketVersion),
description = meta.description
)
}
} else Nil
// Merge
val plugins = (enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false)))
.groupBy(_._1.pluginId)
.map {
case (pluginId, plugins) =>
val (plugin, enabled) = plugins.head
(plugin, enabled, if (plugins.length > 1) plugins.last._1.pluginVersion else "")
}
.toList
html.plugins(plugins, flash.get("info"))
}) })
post("/admin/plugins/_reload")(adminOnly { post("/admin/plugins/_reload")(adminOnly {
@@ -404,36 +349,6 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
redirect("/admin/plugins") redirect("/admin/plugins")
}) })
post("/admin/plugins/:pluginId/:version/_install")(adminOnly {
if (context.settings.pluginNetworkInstall) {
val pluginId = params("pluginId")
val version = params("version")
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
PluginRepository
.getPlugins()
.collectFirst {
case meta if meta.id == pluginId =>
(meta, meta.versions.find(x => x.gitbucketVersion == gitbucketVersion && x.version == version))
}
.foreach {
case (meta, version) =>
version.foreach { version =>
PluginRegistry.install(
pluginId,
new java.net.URL(version.url),
request.getServletContext,
loadSystemSettings(),
request2Session(request).conn
)
flash += "info" -> s"${pluginId}:${version.version} was installed."
}
}
}
redirect("/admin/plugins")
})
get("/admin/users")(adminOnly { get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false) val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)

View File

@@ -227,40 +227,6 @@ object PluginRegistry {
initialize(context, settings, conn) initialize(context, settings, conn)
} }
/**
* Install a plugin from a specified jar file.
*/
def install(
pluginId: String,
url: java.net.URL,
context: ServletContext,
settings: SystemSettings,
conn: java.sql.Connection
): Unit =
synchronized {
shutdown(context, settings)
new File(PluginHome)
.listFiles((_: File, name: String) => {
name.startsWith(s"gitbucket-${pluginId}-plugin") && name.endsWith(".jar")
})
.foreach(_.delete())
withHttpClient(settings.pluginProxy) { httpClient =>
val httpGet = new HttpGet(url.toString)
try {
val response = httpClient.execute(httpGet)
val in = response.getEntity.getContent
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
} finally {
httpGet.releaseConnection()
}
}
instance = new PluginRegistry()
initialize(context, settings, conn)
}
private def listPluginJars(dir: File): Seq[File] = { private def listPluginJars(dir: File): Seq[File] = {
dir dir
.listFiles(new FilenameFilter { .listFiles(new FilenameFilter {

View File

@@ -1,59 +0,0 @@
package gitbucket.core.plugin
import gitbucket.core.controller.Context
import gitbucket.core.util.SyntaxSugars.using
import gitbucket.core.util.HttpClientUtil._
import org.json4s._
import org.apache.commons.io.IOUtils
import org.apache.http.client.methods.HttpGet
import org.slf4j.LoggerFactory
object PluginRepository {
private val logger = LoggerFactory.getLogger(getClass)
implicit val formats = DefaultFormats
def parsePluginJson(json: String): Seq[PluginMetadata] = {
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
}
def getPlugins()(implicit context: Context): Seq[PluginMetadata] = {
try {
val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json")
withHttpClient(context.settings.pluginProxy) { httpClient =>
val httpGet = new HttpGet(url.toString)
try {
val response = httpClient.execute(httpGet)
using(response.getEntity.getContent) { in =>
val str = IOUtils.toString(in, "UTF-8")
parsePluginJson(str)
}
} finally {
httpGet.releaseConnection()
}
}
} catch {
case t: Throwable =>
logger.warn("Failed to access to the plugin repository: " + t.toString)
Nil
}
}
}
// Mapped from plugins.json
case class PluginMetadata(
id: String,
name: String,
description: String,
versions: Seq[VersionDef],
default: Boolean = false
) {
lazy val latestVersion: VersionDef = versions.last
}
case class VersionDef(
version: String,
url: String,
gitbucketVersion: String
)

View File

@@ -69,17 +69,6 @@ trait SystemSettingsService {
} }
props.setProperty(SkinName, settings.skinName.toString) props.setProperty(SkinName, settings.skinName.toString)
props.setProperty(ShowMailAddress, settings.showMailAddress.toString) props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString)
settings.pluginProxy.foreach { proxy =>
props.setProperty(PluginProxyHost, proxy.host)
props.setProperty(PluginProxyPort, proxy.port.toString)
proxy.user.foreach { user =>
props.setProperty(PluginProxyUser, user)
}
proxy.password.foreach { password =>
props.setProperty(PluginProxyPassword, password)
}
}
using(new java.io.FileOutputStream(GitBucketConf)) { out => using(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null) props.store(out, null)
@@ -156,18 +145,7 @@ trait SystemSettingsService {
None None
}, },
getValue(props, SkinName, "skin-blue"), getValue(props, SkinName, "skin-blue"),
getValue(props, ShowMailAddress, false), getValue(props, ShowMailAddress, false)
getValue(props, PluginNetworkInstall, false),
if (getValue(props, PluginProxyHost, "").nonEmpty) {
Some(
Proxy(
getValue(props, PluginProxyHost, ""),
getValue(props, PluginProxyPort, 8080),
getOptionValue(props, PluginProxyUser, None),
getOptionValue(props, PluginProxyPassword, None)
)
)
} else None
) )
} }
} }
@@ -196,9 +174,7 @@ object SystemSettingsService {
oidcAuthentication: Boolean, oidcAuthentication: Boolean,
oidc: Option[OIDC], oidc: Option[OIDC],
skinName: String, skinName: String,
showMailAddress: Boolean, showMailAddress: Boolean
pluginNetworkInstall: Boolean,
pluginProxy: Option[Proxy]
) { ) {
def baseUrl(request: HttpServletRequest): String = def baseUrl(request: HttpServletRequest): String =

View File

@@ -1,4 +1,4 @@
@(plugins: List[(gitbucket.core.plugin.PluginInfoBase, Boolean, String)], info: Option[Any])(implicit context: gitbucket.core.controller.Context) @(plugins: List[gitbucket.core.plugin.PluginInfoBase], info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Plugins"){ @gitbucket.core.html.main("Plugins"){
@gitbucket.core.admin.html.menu("plugins") { @gitbucket.core.admin.html.menu("plugins") {
@gitbucket.core.helper.html.information(info) @gitbucket.core.helper.html.information(info)
@@ -8,26 +8,17 @@
<h1 class="system-settings-title">Plugins</h1> <h1 class="system-settings-title">Plugins</h1>
@if(plugins.size > 0) { @if(plugins.size > 0) {
<ul> <ul>
@plugins.map { case (plugin, enabled, updatableVersion) => @plugins.map { plugin =>
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li> <li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
} }
</ul> </ul>
@plugins.map { case (plugin, enabled, updatableVersion) => @plugins.map { plugin =>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading strong" id="@plugin.pluginId"> <div class="panel-heading strong" id="@plugin.pluginId">
<form method="POST" class="pull-right"> <form method="POST" class="pull-right">
@if(enabled){ <input type="submit" value="Uninstall" class="btn btn-danger btn-sm uninstall-plugin" style="position: relative; top: -5px; left: 10px;"
@if(updatableVersion.nonEmpty){ data-name="@plugin.pluginName" formaction="@{context.path}/admin/plugins/@{plugin.pluginId}/_uninstall">
<input type="submit" value="Update" class="btn btn-success btn-sm update-plugin" style="position: relative; top: -5px; left: 10px;"
data-name="@plugin.pluginName" formaction="@{context.path}/admin/plugins/@{plugin.pluginId}/@{updatableVersion}/_install">
}
<input type="submit" value="Uninstall" class="btn btn-danger btn-sm uninstall-plugin" style="position: relative; top: -5px; left: 10px;"
data-name="@plugin.pluginName" formaction="@{context.path}/admin/plugins/@{plugin.pluginId}/_uninstall">
} else {
<input type="submit" value="Install" class="btn btn-success btn-sm install-plugin" style="position: relative; top: -5px; left: 10px;"
data-name="@plugin.pluginName" formaction="@{context.path}/admin/plugins/@{plugin.pluginId}/@{plugin.pluginVersion}/_install">
}
</form> </form>
@plugin.pluginName @plugin.pluginName
</div> </div>
@@ -58,15 +49,5 @@
var name = $(e.target).data('name'); var name = $(e.target).data('name');
return confirm('Uninstall ' + name + '. Are you sure?'); return confirm('Uninstall ' + name + '. Are you sure?');
}); });
$('.install-plugin').click(function(e){
var name = $(e.target).data('name');
return confirm('Install ' + name + '. Are you sure?');
});
$('.update-plugin').click(function(e){
var name = $(e.target).data('name');
return confirm('Update ' + name + '. Are you sure?');
});
}); });
</script> </script>

View File

@@ -17,9 +17,6 @@
<div class="tab-pane" id="authentication"> <div class="tab-pane" id="authentication">
@settings_authentication(info) @settings_authentication(info)
</div> </div>
<div class="tab-pane" id="plugins">
@settings_plugins(info)
</div>
</div> </div>
<hr> <hr>
<div class="align-right" style="margin-top: 20px;"> <div class="align-right" style="margin-top: 20px;">
@@ -34,9 +31,6 @@ $(function(){
if(location.hash == '#authentication'){ if(location.hash == '#authentication'){
$('li:has(a[href="#authentication"])').addClass('active'); $('li:has(a[href="#authentication"])').addClass('active');
$('div#authentication').addClass('active'); $('div#authentication').addClass('active');
} else if(location.hash == '#plugins'){
$('li:has(a[href="#plugins"])').addClass('active');
$('div#plugins').addClass('active');
} else { } else {
$('li:has(a[href="#system"])').addClass('active'); $('li:has(a[href="#system"])').addClass('active');
$('div#system').addClass('active'); $('div#system').addClass('active');

View File

@@ -1,52 +0,0 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
<label class="strong">Plugin repositories</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="pluginNetworkInstall" name="pluginNetworkInstall"@if(context.settings.pluginNetworkInstall){ checked} />
<a href="https://plugins.gitbucket-community.org" target="_blank">plugins.gitbucket-community.org</a>
</label>
</fieldset>
<hr>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useProxy" name="useProxy"@if(context.settings.pluginProxy.isDefined){ checked} />
Use proxy
</label>
</fieldset>
<div class="proxy">
<div class="form-group">
<label class="control-label col-md-2" for="proxyHost">Proxy host</label>
<div class="col-md-10">
<input type="text" id="proxyHost" name="proxy.host" class="form-control" value="@context.settings.pluginProxy.map(_.host)"/>
<span id="error-proxy_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyPort">Proxy port</label>
<div class="col-md-10">
<input type="text" id="proxyPort" name="proxy.port" class="form-control input-mini" value="@context.settings.pluginProxy.map(_.port)"/>
<span id="error-proxy_port" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyUser">Proxy user</label>
<div class="col-md-10">
<input type="text" id="proxyUser" name="proxy.user" class="form-control" value="@context.settings.pluginProxy.map(_.user)"/>
<span id="error-proxy_user" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyPassword">Proxy password</label>
<div class="col-md-10">
<input type="password" id="proxyPassword" name="proxy.password" class="form-control" value="@context.settings.pluginProxy.map(_.password)"/>
<span id="error-proxy_password" class="error"></span>
</div>
</div>
</div>
<script>
$(function(){
$('#useProxy').change(function(){
$('.proxy input').prop('disabled', !$(this).prop('checked'));
}).change();
});
</script>

View File

@@ -53,9 +53,7 @@ trait ServiceSpecBase extends MockitoSugar {
oidcAuthentication = false, oidcAuthentication = false,
oidc = None, oidc = None,
skinName = "skin-blue", skinName = "skin-blue",
showMailAddress = false, showMailAddress = false
pluginNetworkInstall = false,
pluginProxy = None
) )
def withTestDB[A](action: (Session) => A): A = { def withTestDB[A](action: (Session) => A): A = {

View File

@@ -137,9 +137,7 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
oidcAuthentication = false, oidcAuthentication = false,
oidc = None, oidc = None,
skinName = "skin-blue", skinName = "skin-blue",
showMailAddress = false, showMailAddress = false
pluginNetworkInstall = false,
pluginProxy = None
) )
/** /**