From 1522f18abeb045993d95477f8dbc9e3a04b4a4c6 Mon Sep 17 00:00:00 2001 From: "Francois @fanf42 Armand" Date: Wed, 5 Jan 2022 18:55:28 +0100 Subject: [PATCH 1/2] Fixes #20261: Add a warning in plugin page if a version mismatches rudder patch one --- .../normation/plugins/RudderPluginTest.scala | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala new file mode 100644 index 00000000000..bce618b0180 --- /dev/null +++ b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala @@ -0,0 +1,172 @@ +/* +************************************************************************************* +* Copyright 2019 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ + +package com.normation.plugins + +import org.joda.time.DateTime +import org.joda.time.format.ISODateTimeFormat +import org.junit.runner.RunWith +import org.specs2.mutable._ +import org.specs2.runner.JUnitRunner + +import com.normation.zio._ + +@RunWith(classOf[JUnitRunner]) +class RudderPluginTest extends Specification { + + val index_json = """{ + | "plugins": { + | "rudder-plugin-branding": { + | "files": [ + | "/opt/rudder/share/plugins/", + | "/opt/rudder/share/plugins/branding/", + | "/opt/rudder/share/plugins/branding/branding.jar" + | ], + | "name": "rudder-plugin-branding", + | "jar-files": [ + | "/opt/rudder/share/plugins/branding/branding.jar" + | ], + | "content": { + | "files.txz": "/opt/rudder/share/plugins" + | }, + | "version": "5.0-1.3", + | "build-commit": "81edd3edf4f28c13821af8014da0520b72b9df94", + | "build-date": "2018-10-11T12:23:40+02:00", + | "type": "plugin" + | }, + | "rudder-plugin-centreon": { + | "files": [ + | "/opt/rudder//", + | "/opt/rudder//bin/", + | "/opt/rudder//bin/centreon-plugin", + | "/opt/rudder//share/", + | "/opt/rudder//share/python/", + | "/opt/rudder//share/python/centreonapi/", + | "/opt/rudder//share/python/centreonapi/__init__.py", + | "/opt/rudder//share/python/centreonapi/webservice/", + | "/opt/rudder//share/python/centreonapi/webservice/__init__.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/__init__.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/hostgroups.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/service.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/templates.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/poller.py", + | "/opt/rudder//share/python/centreonapi/webservice/configuration/host.py", + | "/opt/rudder//share/python/centreonapi/centreon.py", + | "/opt/rudder//share/python/centreonapi/__init__.pyc", + | "/opt/rudder//share/python/ipaddress.py", + | "/opt/rudder//etc/", + | "/opt/rudder//etc/hooks.d/", + | "/opt/rudder//etc/hooks.d/node-pre-deletion/", + | "/opt/rudder//etc/hooks.d/node-pre-deletion/centreon-pre-deletion.sh", + | "/opt/rudder//etc/hooks.d/node-post-acceptance/", + | "/opt/rudder//etc/hooks.d/node-post-acceptance/centreon-post-acceptance.sh" + | ], + | "name": "rudder-plugin-centreon", + | "content": { + | "files.txz": "/opt/rudder/" + | }, + | "version": "5.0-1.1", + | "build-commit": "5c4592d93912ef56de0c506295d22fb2a86146ac", + | "build-date": "2018-10-29T18:34:16+01:00", + | "type": "plugin" + | } + | } + |}""".stripMargin + + val expected = List( + JsonPluginDef( + "rudder-plugin-branding" + , PluginVersion(1, 3, 0, "5.0-") + , List( + "/opt/rudder/share/plugins/", + "/opt/rudder/share/plugins/branding/", + "/opt/rudder/share/plugins/branding/branding.jar" + ) + , List("/opt/rudder/share/plugins/branding/branding.jar") + , "81edd3edf4f28c13821af8014da0520b72b9df94" + , DateTime.parse("2018-10-11T12:23:40+02:00", ISODateTimeFormat.dateTimeNoMillis()) + ) + , JsonPluginDef( + "rudder-plugin-centreon" + , PluginVersion(1, 1, 0, "5.0-") + , List( + "/opt/rudder//", + "/opt/rudder//bin/", + "/opt/rudder//bin/centreon-plugin", + "/opt/rudder//share/", + "/opt/rudder//share/python/", + "/opt/rudder//share/python/centreonapi/", + "/opt/rudder//share/python/centreonapi/__init__.py", + "/opt/rudder//share/python/centreonapi/webservice/", + "/opt/rudder//share/python/centreonapi/webservice/__init__.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/", + "/opt/rudder//share/python/centreonapi/webservice/configuration/__init__.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/hostgroups.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/service.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/templates.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/poller.py", + "/opt/rudder//share/python/centreonapi/webservice/configuration/host.py", + "/opt/rudder//share/python/centreonapi/centreon.py", + "/opt/rudder//share/python/centreonapi/__init__.pyc", + "/opt/rudder//share/python/ipaddress.py", + "/opt/rudder//etc/", + "/opt/rudder//etc/hooks.d/", + "/opt/rudder//etc/hooks.d/node-pre-deletion/", + "/opt/rudder//etc/hooks.d/node-pre-deletion/centreon-pre-deletion.sh", + "/opt/rudder//etc/hooks.d/node-post-acceptance/", + "/opt/rudder//etc/hooks.d/node-post-acceptance/centreon-post-acceptance.sh" + ) + , List() + , "5c4592d93912ef56de0c506295d22fb2a86146ac" + , DateTime.parse("2018-10-29T18:34:16+01:00", ISODateTimeFormat.dateTimeNoMillis()) + ) + ) + + val packageService = new ReadPluginPackageInfo("/tmp/foo") + + "Plugins JSON service" should { + "be able to read json file format" in { + val all = packageService.parseJson(index_json).runNow + val success = all.collect { case Right(x) => x } + val errors = all.collect { case Left(x) => x } + + (errors must beEmpty) and + (success must containTheSameElementsAs(expected)) + } + } +} From 3680c03a186d59d26985a4b319e59d7f326b369b Mon Sep 17 00:00:00 2001 From: "Francois @fanf42 Armand" Date: Wed, 5 Jan 2022 20:45:16 +0100 Subject: [PATCH 2/2] Fixes #20261: Add a warning in plugin page if a version mismatches rudder patch one --- .../main/scala/bootstrap/liftweb/Boot.scala | 7 +- .../com/normation/plugins/PublicPlugin.scala | 39 +++-- .../com/normation/plugins/RudderPlugin.scala | 88 +++++++---- .../administration/PluginManagement.scala | 13 +- .../secure/plugins/pluginInformation.html | 5 +- .../plugins/RudderPluginJsonTest.scala | 13 +- .../normation/plugins/RudderPluginTest.scala | 141 +++--------------- .../scala/com/normation/utils/Version.scala | 5 +- 8 files changed, 132 insertions(+), 179 deletions(-) diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala index c6f87cd093c..d1afff5a35e 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala @@ -50,13 +50,13 @@ import com.normation.rudder.web.model.CurrentUser import com.normation.rudder.AuthorizationType import com.normation.rudder.domain.logger.ApplicationLogger import com.normation.eventlog.ModificationId -import java.util.Locale +import java.util.Locale import net.liftweb.http.rest.RestHelper import org.joda.time.DateTime import com.normation.rudder.web.snippet.WithCachedResource -import java.net.URLConnection +import java.net.URLConnection import com.normation.inventory.domain.InventoryProcessingLogger import com.normation.plugins.AlwaysEnabledPluginStatus import com.normation.plugins.RudderPluginModule @@ -70,11 +70,12 @@ import com.normation.rudder.rest.EndpointSchema import com.normation.rudder.rest.{InfoApi => InfoApiDef} import com.normation.rudder.rest.lift.InfoApi import com.normation.rudder.rest.lift.LiftApiModuleProvider + import net.liftweb.sitemap.Loc.LocGroup import net.liftweb.sitemap.Loc.TestAccess import org.reflections.Reflections -import com.normation.zio._ +import com.normation.zio._ import scala.xml.NodeSeq import scala.xml.NodeSeq.seqToNodeSeq diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/PublicPlugin.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/PublicPlugin.scala index 32cd8e3872f..097a56659d1 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/PublicPlugin.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/PublicPlugin.scala @@ -67,18 +67,32 @@ trait DefaultPluginDef extends RudderPluginDef { //get properties name for the plugin from "build.conf" file //have default string for errors (and avoid "missing prop exception"): - lazy val defaults = List( - "plugin-name" - , "plugin-fullname" - , "plugin-title-description" - , "plugin-version" - ).map(p => s"$p=missing property with name '$p' in file 'build.conf' for '${basePackage}'").mkString("\n") - - //by convention, plugin "build.conf" file is copied into path: + lazy val defaults = { + val d1 = List( + "plugin-name" + , "plugin-fullname" + , "plugin-title-description" + , "plugin-version" + ).map(p => s"$p=missing property with name '$p' in file 'build.conf' for '${basePackage}'").mkString("\n") + + val d2 = List( + "branch-type" + , "rudder-version" + , "common-version" + , "private-version" + ).map(p => s"$p=missing property with name '$p' in file 'main-build.conf' for '${basePackage}'").mkString("\n") + + val res = d1 + "\n" + d2 + res + } + + //by convention, plugin "build.conf" and plugin-commons "main-build.conf" files are copied into path: // target/classes/com/normation/plugins/${project.artifactId} lazy val buildConfPath = basePackage.replaceAll("""\.""", "/") + "/build.conf" + lazy val mainBuildConfPath = basePackage.replaceAll("""\.""", "/") + "/main-build.conf" lazy val buildConf = try { - ConfigFactory.load(this.getClass.getClassLoader, buildConfPath).withFallback(ConfigFactory.parseString(defaults)) + val c1 = ConfigFactory.load(this.getClass.getClassLoader, buildConfPath).withFallback(ConfigFactory.parseString(defaults)) + ConfigFactory.load(this.getClass.getClassLoader, mainBuildConfPath).withFallback(c1) } catch { case ex: ConfigException => //something want very wrong with "build.conf" parsing @@ -90,11 +104,8 @@ trait DefaultPluginDef extends RudderPluginDef { override lazy val shortName = buildConf.getString("plugin-name") override lazy val displayName = buildConf.getString("plugin-title-description") override lazy val version = { - val versionString = buildConf.getString("plugin-version") - PluginVersion.from(versionString).getOrElse( - //a version name that indicate an erro - PluginVersion(0,0,1, s"ERROR-PARSING-VERSION: ${versionString}") - ) + val versionString = buildConf.getString("rudder-version") + "-" + buildConf.getString("plugin-version") + PluginVersion.from(versionString).getOrElse(PluginVersion.PARSING_ERROR(versionString)) } override lazy val versionInfo = { try { diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala index 2c17c327989..2fc4f9e1e93 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/plugins/RudderPlugin.scala @@ -39,61 +39,81 @@ package com.normation.plugins import scala.xml.NodeSeq import com.normation.rudder.domain.logger.ApplicationLogger + import com.typesafe.config.{Config, ConfigFactory} import bootstrap.liftweb.{ClassPathResource, ConfigResource, FileSystemResource, RudderProperties} import com.normation.rudder.rest.EndpointSchema import com.normation.rudder.rest.lift.LiftApiModuleProvider +import com.normation.utils._ +import com.normation.utils.PartType._ +import com.normation.utils.VersionPart._ +import com.normation.utils.Separator.Dot +import com.normation.utils.Separator.Minus + import net.liftweb.sitemap.Menu -final case class PluginVersion( - major : Int - , minor : Int - , micro : Int - , prefix : String = "" - , suffix : String = "" -) { +final case class PluginVersion private (rudderAbi: Version, pluginVersion: Version) { - override def toString = prefix + major + "." + minor + "." + micro + suffix + override def toString = rudderAbi.toVersionStringNoEpoch + "-" + pluginVersion.toVersionString } object PluginVersion { + + // a special value used to indicate a plugin version parsing error + def PARSING_ERROR(badVersion: String) = { + val vr = Version(0, Numeric(0), After(Dot, Numeric(0)) :: After(Dot, Numeric(1)) :: Nil) + val vp = Version(0, Numeric(0), After(Dot, Numeric(0)) :: After(Dot, Numeric(1)) :: After(Minus, Chars("ERROR-PARSING-VERSION: " + badVersion)) :: Nil) + new PluginVersion(vr, vp) + } + /* * That method will create a plugin version from a string. * It may fails if the pattern is not one known for rudder plugin, which is: - * (A.B-)x.y(.z)(post) + * (A.B(.C)(post1))-(x.y(.z)(post2)) * Where part between parenthesis are optionnal, * A,B,x,y,z are non-empty list of digits, - * A.B are Rudder major.minor version, - * x.y are plugin major.minor.micro version - if micro not specified, assumed to be 0, - * post is any non blank/control char list (ex: ~alpha1) + * A.B(.C)(post1) are Rudder major.minor.patch version, with patch = 0 if not specified + * x.y(.z) are plugin major.minor.patch version - if patch not specified, assumed to be 0, + * postN is any non blank/control char list (ex: ~alpha1) * */ def from(version: String): Option[PluginVersion] = { - //carefull: group matching nothing, like optionnal group, return null :( - def nonNull(s: String) = s match { - case null => "" - case x => x - } - - val pattern = """(\d+\.\d+-)?(\d+)\.(\d+)(\.(\d+))?(\S+)?""".r.pattern + // the structure of our plugin is not really version-parsing friendly. + // We need to split on "-" which can also be a postfix separator. So + // we assume that there is always a digit just after the rudder/plugin "-" separator. And that the + // pattern "-digit" happens only one time. + val pattern = """(\S+)-(\d\S+)?""".r.pattern val matcher = pattern.matcher(version) if( matcher.matches ) { - val micro = matcher.group(5) match { - case null | "" => 0 - case x => x.toInt + + (ParseVersion.parse(matcher.group(1)), ParseVersion.parse(matcher.group(2))) match { + case (Right(rv), Right(pv)) => Some(PluginVersion(rv, pv)) + case (x, y) => None } - Some(PluginVersion( - matcher.group(2).toInt - , matcher.group(3).toInt - , micro - , nonNull(matcher.group(1)) - , nonNull(matcher.group(6)) - )) + } else { None } } + + // normalize rudderVersion and pluginVersion to have at least 3 digits + def normalize(v: Version): Version = { + v match { + // at least 3 digits + case ok@Version(_, _, After(Dot, _:Numeric) :: After(Dot, _:Numeric) :: tail) => ok + // only 2 - add one 0 + case Version(epoch, major, After(Dot, minor:Numeric) :: tail) => + Version(epoch, major, After(Dot, minor) :: After(Dot, Numeric(0)) :: tail) + // only 1 - add two 0 + case Version(epoch, major, tail) => + Version(epoch, major, After(Dot, Numeric(0)) :: After(Dot, Numeric(0)) :: tail) + } + } + + def apply(rudderVersion: Version, pluginVersion: Version) = { + new PluginVersion(normalize(rudderVersion), normalize(pluginVersion)) + } } final case class PluginName(value:String) { @@ -142,7 +162,12 @@ trait RudderPluginDef { def description : NodeSeq /** - * Version of the plugin. + * Full (i.e with Rudder version) version of the plugin. + * For example: 7.1.5-2.3.0 for a plugin in version 2.3.0 compile against rudder 7.1.5. + * + * It is composed of rudderAbi version: this is the version of rudder used to build the plugin + * (ie the 7.1.5 part in version example) + * And of plugin own version (ie the 2.3.0 part in version example). */ def version : PluginVersion @@ -151,9 +176,6 @@ trait RudderPluginDef { */ def versionInfo : Option[String] - - - /* * Information about the plugin activation status * and license information. diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/PluginManagement.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/PluginManagement.scala index f049ec81e73..741610c90de 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/PluginManagement.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/PluginManagement.scala @@ -45,6 +45,7 @@ import scala.xml.NodeSeq import com.normation.plugins.RudderPluginDef import bootstrap.liftweb.PluginsInfo +import bootstrap.liftweb.RudderConfig class PluginManagement extends DispatchSnippet with Loggable { @@ -62,6 +63,14 @@ class PluginManagement extends DispatchSnippet with Loggable { } private[this] def displayPlugin(p:RudderPluginDef)(xml:NodeSeq) : NodeSeq = { + val rudderPluginVersion = p.version.rudderAbi.toVersionStringNoEpoch + // we compare on string, since we are just looking for an exact match. + val versionWarning = if(RudderConfig.rudderFullVersion != rudderPluginVersion) { + + WARNING! This plugin was not build for current Rudder ABI version ({RudderConfig.rudderFullVersion}). You should update it to avoid code incompatibilities. + + } else NodeSeq.Empty + ( "data-plugin=name" #> { p.versionInfo match { @@ -76,7 +85,9 @@ class PluginManagement extends DispatchSnippet with Loggable { } & "data-plugin=fullid" #> p.name.value & "data-plugin=baseclass" #> p.id & - "data-plugin=version" #> p.version.toString & + "data-plugin=version" #> p.version.pluginVersion.toVersionStringNoEpoch & + "data-plugin=rudderVersion" #> p.version.rudderAbi.toVersionStringNoEpoch & + "data-plugin=versionWarning" #> versionWarning & "data-plugin=description" #> p.description & "data-plugin=statusInformation" #> p.statusInformation() )(xml) diff --git a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/plugins/pluginInformation.html b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/plugins/pluginInformation.html index cacad9e53b5..2e705fc89a0 100644 --- a/webapp/sources/rudder/rudder-web/src/main/webapp/secure/plugins/pluginInformation.html +++ b/webapp/sources/rudder/rudder-web/src/main/webapp/secure/plugins/pluginInformation.html @@ -32,7 +32,10 @@

[Here comes the plugins name]

[Here comes the plugin description]

  • Plugin ID: [Here comes the plugin full id]
  • -
  • Plugin version: [Here comes the plugin version]
  • +
  • Plugin version: [Here comes the plugin version]
  • +
  • Rudder ABI version: [Here comes the rudder version used by the plugin] + [warning message if patch version of the plugin is not the same as Rudder] +
diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala index ad18a843ece..eeb09a4c1e2 100644 --- a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala +++ b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginJsonTest.scala @@ -37,15 +37,24 @@ package com.normation.plugins +import com.normation.utils.ParseVersion + import org.junit.runner.RunWith import org.specs2.mutable._ import org.specs2.runner.JUnitRunner + import com.normation.zio._ import org.joda.time.DateTime import org.joda.time.format.ISODateTimeFormat @RunWith(classOf[JUnitRunner]) class RudderPluginJsonTest extends Specification { + implicit class ForceParse(s: String) { + def toVersion = ParseVersion.parse(s) match { + case Left(err) => throw new IllegalArgumentException(s"Can not parse '${s}' as a version in test: ${err}") + case Right(v) => v + } + } val index_json = """{ | "plugins": { @@ -110,7 +119,7 @@ class RudderPluginJsonTest extends Specification { val expected = List( JsonPluginDef( "rudder-plugin-branding" - , PluginVersion(1, 3, 0, "5.0-") + , PluginVersion("5.0.0".toVersion, "1.3.0".toVersion) , List( "/opt/rudder/share/plugins/", "/opt/rudder/share/plugins/branding/", @@ -122,7 +131,7 @@ class RudderPluginJsonTest extends Specification { ) , JsonPluginDef( "rudder-plugin-centreon" - , PluginVersion(1, 1, 0, "5.0-") + , PluginVersion("5.0.0".toVersion, "1.1.0".toVersion) , List( "/opt/rudder//", "/opt/rudder//bin/", diff --git a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala index bce618b0180..2b2d3849cb1 100644 --- a/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala +++ b/webapp/sources/rudder/rudder-web/src/test/scala/com/normation/plugins/RudderPluginTest.scala @@ -1,6 +1,6 @@ /* ************************************************************************************* -* Copyright 2019 Normation SAS +* Copyright 2022 Normation SAS ************************************************************************************* * * This file is part of Rudder. @@ -37,136 +37,31 @@ package com.normation.plugins -import org.joda.time.DateTime -import org.joda.time.format.ISODateTimeFormat +import com.normation.utils.ParseVersion + import org.junit.runner.RunWith import org.specs2.mutable._ import org.specs2.runner.JUnitRunner -import com.normation.zio._ - @RunWith(classOf[JUnitRunner]) class RudderPluginTest extends Specification { - val index_json = """{ - | "plugins": { - | "rudder-plugin-branding": { - | "files": [ - | "/opt/rudder/share/plugins/", - | "/opt/rudder/share/plugins/branding/", - | "/opt/rudder/share/plugins/branding/branding.jar" - | ], - | "name": "rudder-plugin-branding", - | "jar-files": [ - | "/opt/rudder/share/plugins/branding/branding.jar" - | ], - | "content": { - | "files.txz": "/opt/rudder/share/plugins" - | }, - | "version": "5.0-1.3", - | "build-commit": "81edd3edf4f28c13821af8014da0520b72b9df94", - | "build-date": "2018-10-11T12:23:40+02:00", - | "type": "plugin" - | }, - | "rudder-plugin-centreon": { - | "files": [ - | "/opt/rudder//", - | "/opt/rudder//bin/", - | "/opt/rudder//bin/centreon-plugin", - | "/opt/rudder//share/", - | "/opt/rudder//share/python/", - | "/opt/rudder//share/python/centreonapi/", - | "/opt/rudder//share/python/centreonapi/__init__.py", - | "/opt/rudder//share/python/centreonapi/webservice/", - | "/opt/rudder//share/python/centreonapi/webservice/__init__.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/__init__.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/hostgroups.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/service.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/templates.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/poller.py", - | "/opt/rudder//share/python/centreonapi/webservice/configuration/host.py", - | "/opt/rudder//share/python/centreonapi/centreon.py", - | "/opt/rudder//share/python/centreonapi/__init__.pyc", - | "/opt/rudder//share/python/ipaddress.py", - | "/opt/rudder//etc/", - | "/opt/rudder//etc/hooks.d/", - | "/opt/rudder//etc/hooks.d/node-pre-deletion/", - | "/opt/rudder//etc/hooks.d/node-pre-deletion/centreon-pre-deletion.sh", - | "/opt/rudder//etc/hooks.d/node-post-acceptance/", - | "/opt/rudder//etc/hooks.d/node-post-acceptance/centreon-post-acceptance.sh" - | ], - | "name": "rudder-plugin-centreon", - | "content": { - | "files.txz": "/opt/rudder/" - | }, - | "version": "5.0-1.1", - | "build-commit": "5c4592d93912ef56de0c506295d22fb2a86146ac", - | "build-date": "2018-10-29T18:34:16+01:00", - | "type": "plugin" - | } - | } - |}""".stripMargin - - val expected = List( - JsonPluginDef( - "rudder-plugin-branding" - , PluginVersion(1, 3, 0, "5.0-") - , List( - "/opt/rudder/share/plugins/", - "/opt/rudder/share/plugins/branding/", - "/opt/rudder/share/plugins/branding/branding.jar" - ) - , List("/opt/rudder/share/plugins/branding/branding.jar") - , "81edd3edf4f28c13821af8014da0520b72b9df94" - , DateTime.parse("2018-10-11T12:23:40+02:00", ISODateTimeFormat.dateTimeNoMillis()) - ) - , JsonPluginDef( - "rudder-plugin-centreon" - , PluginVersion(1, 1, 0, "5.0-") - , List( - "/opt/rudder//", - "/opt/rudder//bin/", - "/opt/rudder//bin/centreon-plugin", - "/opt/rudder//share/", - "/opt/rudder//share/python/", - "/opt/rudder//share/python/centreonapi/", - "/opt/rudder//share/python/centreonapi/__init__.py", - "/opt/rudder//share/python/centreonapi/webservice/", - "/opt/rudder//share/python/centreonapi/webservice/__init__.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/", - "/opt/rudder//share/python/centreonapi/webservice/configuration/__init__.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/hostgroups.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/service.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/templates.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/poller.py", - "/opt/rudder//share/python/centreonapi/webservice/configuration/host.py", - "/opt/rudder//share/python/centreonapi/centreon.py", - "/opt/rudder//share/python/centreonapi/__init__.pyc", - "/opt/rudder//share/python/ipaddress.py", - "/opt/rudder//etc/", - "/opt/rudder//etc/hooks.d/", - "/opt/rudder//etc/hooks.d/node-pre-deletion/", - "/opt/rudder//etc/hooks.d/node-pre-deletion/centreon-pre-deletion.sh", - "/opt/rudder//etc/hooks.d/node-post-acceptance/", - "/opt/rudder//etc/hooks.d/node-post-acceptance/centreon-post-acceptance.sh" - ) - , List() - , "5c4592d93912ef56de0c506295d22fb2a86146ac" - , DateTime.parse("2018-10-29T18:34:16+01:00", ISODateTimeFormat.dateTimeNoMillis()) - ) - ) - - val packageService = new ReadPluginPackageInfo("/tmp/foo") - - "Plugins JSON service" should { - "be able to read json file format" in { - val all = packageService.parseJson(index_json).runNow - val success = all.collect { case Right(x) => x } - val errors = all.collect { case Left(x) => x } + implicit class ForceParse(s: String) { + def toVersion = ParseVersion.parse(s) match { + case Left(err) => throw new IllegalArgumentException(s"Can not parse '${s}' as a version in test: ${err}") + case Right(v) => v + } + } - (errors must beEmpty) and - (success must containTheSameElementsAs(expected)) + "Parsing a plugin version" should { + "be able to read simple rudder version" in { + PluginVersion.from("7.1.0-2.3.0") must_!= (PluginVersion("7.1.0".toVersion, "2.3.0".toVersion)) + } + "automatically add a patch level (eq 0)" in { + PluginVersion.from("7.1-2.3") must_!= (PluginVersion("7.1.0".toVersion, "2.3.0".toVersion)) + } + "understand complicated format with rc" in { + PluginVersion.from("7.0.0~rc2-SNAPSHOT-2.1-nightly") must_!= (PluginVersion("7.0.0~rc2-SNAPSHOT".toVersion, "2.1.0-nightly".toVersion)) } } } diff --git a/webapp/sources/utils/src/main/scala/com/normation/utils/Version.scala b/webapp/sources/utils/src/main/scala/com/normation/utils/Version.scala index db54f42f85e..000bdc8703a 100644 --- a/webapp/sources/utils/src/main/scala/com/normation/utils/Version.scala +++ b/webapp/sources/utils/src/main/scala/com/normation/utils/Version.scala @@ -169,7 +169,8 @@ sealed trait VersionPart extends ToVersionString with Ordered[VersionPart] { override def toVersionString: String = separator.toVersionString + value.toVersionString override def compare(other: VersionPart): Int = VersionPart.compare(this, other) - } +} + object VersionPart { final case class Before(separator: Separator, value: PartType) extends VersionPart //we can have before with "-" separator for ex with alpha, etc final case class After (separator: Separator, value: PartType) extends VersionPart @@ -181,7 +182,7 @@ object VersionPart { val c = Separator.compare(a.separator, b.separator) // not sure? Does 1.0~alpha < 1.0.alpha ? if(c == 0) PartType.compare(a.value, b.value) else c } - } +} object ParseVersion { import fastparse._, NoWhitespace._