From 96ab8d080b12b8486a0764e4c90bd37e2ff03c16 Mon Sep 17 00:00:00 2001 From: Felix Satyaputra Date: Thu, 29 Oct 2015 12:26:40 +1100 Subject: [PATCH] Relocatable rpm symlink fix When creating relocatable RPM packages, the symlink needs to take into account the relocated file paths. RPM does not provide out of the box support for this, hence the pull request to manage symlinks as part of %post and %postun scriptlet. This will ensure the symlink will be created and removed as part of post-install and post-uninstall step. When creating the symlink, both the source path and the symlink itself need to take into account the relocated path. --- .../archetypes/JavaServerApplication.scala | 1 - .../sbt/packager/linux/LinuxPlugin.scala | 5 +- .../typesafe/sbt/packager/rpm/RpmHelper.scala | 3 - .../sbt/packager/rpm/RpmMetadata.scala | 100 ++++++++++++++++-- .../typesafe/sbt/packager/rpm/RpmPlugin.scala | 2 +- .../sbt/packager/universal/Archives.scala | 3 +- src/sbt-test/rpm/scriptlets-rpm/build.sbt | 34 ++++++ 7 files changed, 133 insertions(+), 15 deletions(-) diff --git a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala index 40752344e..827324168 100644 --- a/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala +++ b/src/main/scala/com/typesafe/sbt/packager/archetypes/JavaServerApplication.scala @@ -295,5 +295,4 @@ object JavaServerAppPackaging extends AutoPlugin { case script => TemplateWriter generateScriptFromString (content + script, replacements) } } - } diff --git a/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala index 434aff158..bee0d70fb 100644 --- a/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/linux/LinuxPlugin.scala @@ -79,7 +79,7 @@ object LinuxPlugin extends AutoPlugin { author = (maintainer in Linux).value, description = (packageSummary in Linux).value, execScript = (executableScriptName in Linux).value, - chdir = s"${defaultLinuxInstallLocation.value}/${(packageName in Linux).value}", + chdir = chdir(defaultLinuxInstallLocation.value, (packageName in Linux).value), logdir = defaultLinuxLogsLocation.value, appName = (packageName in Linux).value, version = sbt.Keys.version.value, @@ -249,4 +249,7 @@ object LinuxPlugin extends AutoPlugin { ) } + final def chdir(installLocation: String, packageName: String): String = + s"$installLocation/$packageName" + } diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala index b375e6042..54bd8465c 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala @@ -48,9 +48,6 @@ object RpmHelper { if file.exists && !file.isDirectory() target = buildroot / dest } copyWithZip(file, target, mapping.zipped) - - // Now we create symlinks - LinuxSymlink.makeSymLinks(spec.symlinks, buildroot) } private[this] def writeSpecFile(spec: RpmSpec, workArea: File, log: sbt.Logger): File = { diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala index 0a70b45d0..fc14ee647 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala @@ -2,9 +2,8 @@ package com.typesafe.sbt package packager package rpm -import linux.{ LinuxPackageMapping, LinuxFileMetaData } +import com.typesafe.sbt.packager.linux.{ LinuxPlugin, LinuxPackageMapping, LinuxFileMetaData, LinuxSymlink } import sbt._ -import com.typesafe.sbt.packager.linux.LinuxSymlink import java.io.File case class RpmMetadata( @@ -59,6 +58,47 @@ case class RpmScripts( posttrans: Option[String] = None, preun: Option[String] = None, postun: Option[String] = None) { + + def pretransContent(): String = + pretrans.fold("")("\n%pretrans\n" + _ + "\n\n") + + def preContent(): String = + pre.fold("")("\n%pre\n" + _ + "\n\n") + + def postContent(buildSymlinkScript: Option[String]): String = { + val scripts = Seq(post, buildSymlinkScript).collect { + case Some(value) => value + } + + if (scripts.isEmpty) + "" + else + "\n%post\n" + scripts.mkString("\n") + "\n\n" + } + + def posttransContent(): String = + posttrans.fold("")("\n%posttrans\n" + _ + "\n\n") + + def verifyscriptContent(): String = + verifyscript.fold("")("\n%verifyscript\n" + _ + "\n\n") + + def preunContent(): String = + preun.fold("")("\n%preun\n" + _ + "\n\n") + + def postunContent(tearDownSymlinkScript: Option[String]): String = { + val scripts = Seq(postun, tearDownSymlinkScript).collect { + case Some(value) => value + } + + if (scripts.isEmpty) + "" + else + "\n%postun\n" + scripts.mkString("\n") + "\n\n" + } + + @deprecated( + "Call individual scriptlet content method instead, e.g. pretransContent(). This is to allow managing symlink during %post and %postun so it can be relocated", + since = "1.0.5-M4") def contents(): String = { val labelledScripts = Seq("%pretrans", "%pre", "%post", "%verifyscript", "%posttrans", "%preun", "%postun") .zip(Seq(pretrans, pre, post, verifyscript, posttrans, preun, postun)) @@ -73,7 +113,11 @@ case class RpmSpec( deps: RpmDependencies = RpmDependencies(), scriptlets: RpmScripts = RpmScripts(), mappings: Seq[LinuxPackageMapping] = Seq.empty, - symlinks: Seq[LinuxSymlink] = Seq.empty) { + symlinks: Seq[LinuxSymlink] = Seq.empty, + installLocation: String) { + + def installDir: String = + LinuxPlugin.chdir(installLocation, meta.name) // TODO - here we want to validate that all the data we have is ok to place // in the RPM. e.g. the Description/vendor etc. must meet specific requirements. @@ -141,9 +185,6 @@ case class RpmSpec( mapping <- mappings (file, dest) <- mapping.mappings } sb append makeFilesLine(dest, mapping.fileData, file.isDirectory) - for { - link <- symlinks - } sb append (fixFilename(link.link) + "\n") sb.toString } @@ -199,7 +240,13 @@ case class RpmSpec( // TODO - Allow symlinks // write scriptlets - sb append scriptlets.contents() + sb append scriptlets.pretransContent() + sb append scriptlets.preContent() + sb append scriptlets.postContent(buildSymlinkScript(meta.name, installDir, symlinks)) + sb append scriptlets.verifyscriptContent() + sb append scriptlets.posttransContent() + sb append scriptlets.preunContent() + sb append scriptlets.postunContent(teardownSymlinkScript(meta.name, installDir, symlinks)) // Write file mappings sb append fileSection @@ -215,4 +262,43 @@ case class RpmSpec( } sb.toString } + + private def buildSymlinkScript(appName: String, installDir: String, symlinks: Seq[LinuxSymlink]): Option[String] = + if (symlinks.isEmpty) + None + else { + val relocateLinks = symlinks + .map { symlink => + s"""rm -rf $$(relocateLink ${symlink.link} $installDir $appName $$RPM_INSTALL_PREFIX) && ln -s $$(relocateLink ${symlink.destination} $installDir $appName $$RPM_INSTALL_PREFIX) $$(relocateLink ${symlink.link} $installDir $appName $$RPM_INSTALL_PREFIX)""" + } + .mkString("\n") + + Some(relocateLinkFunction + "\n" + relocateLinks) + } + + private def teardownSymlinkScript(appName: String, installDir: String, symlinks: Seq[LinuxSymlink]): Option[String] = + if (symlinks.isEmpty) + None + else { + val sourceAppConfig = s"""[ -e /etc/sysconfig/$appName ] && . /etc/sysconfig/$appName""" + val cleanupLinks = symlinks + .map { symlink => + s"""rm -rf $$(relocateLink ${symlink.link} $installDir $appName $$PACKAGE_PREFIX)""" + } + .mkString("\n") + + Some(relocateLinkFunction + "\n" + sourceAppConfig + "\n" + cleanupLinks) + } + + private def relocateLinkFunction: String = + """ + |relocateLink() { + | if [ -n "$4" ] ; + | then + | RELOCATED_INSTALL_DIR="$4/$3" + | echo "${1/$2/$RELOCATED_INSTALL_DIR}" + | else + | echo "$1" + | fi + |}""".stripMargin } diff --git a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala index 002fb2ee3..f4d8cacf0 100644 --- a/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/rpm/RpmPlugin.scala @@ -102,7 +102,7 @@ object RpmPlugin extends AutoPlugin { rpmScripts <<= (rpmPretrans, rpmPre, rpmPost, rpmVerifyscript, rpmPosttrans, rpmPreun, rpmPostun) apply RpmScripts, rpmSpecConfig <<= - (rpmMetadata, rpmDescription, rpmDependencies, rpmScripts, linuxPackageMappings, linuxPackageSymlinks) map RpmSpec, + (rpmMetadata, rpmDescription, rpmDependencies, rpmScripts, linuxPackageMappings, linuxPackageSymlinks, defaultLinuxInstallLocation) map RpmSpec, packageBin <<= (rpmSpecConfig, target, streams) map { (spec, dir, s) => spec.validate(s.log) RpmHelper.buildRpm(spec, dir, s.log) diff --git a/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala b/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala index 1fd992c36..d091c2f65 100644 --- a/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala +++ b/src/main/scala/com/typesafe/sbt/packager/universal/Archives.scala @@ -208,8 +208,7 @@ object Archives { def makeTarball(compressor: File => File, ext: String)(target: File, name: String, mappings: Seq[(File, String)], top: Option[String]): File = makeTarballWithOptions(compressor, ext)(target, name, mappings, top, options = Seq("--force-local", "-pcvf")) - - /** + /** * Helper method used to construct tar-related compression functions. * @param target folder to build package in * @param name of output (without extension) diff --git a/src/sbt-test/rpm/scriptlets-rpm/build.sbt b/src/sbt-test/rpm/scriptlets-rpm/build.sbt index c0254a930..ed8e43a94 100644 --- a/src/sbt-test/rpm/scriptlets-rpm/build.sbt +++ b/src/sbt-test/rpm/scriptlets-rpm/build.sbt @@ -35,10 +35,44 @@ TaskKey[Unit]("check-spec-file") <<= (target, streams) map { (target, out) => val spec = IO.read(target / "rpm" / "SPECS" / "rpm-test.spec") assert(spec contains "%pre\necho \"pre-install\"", "Spec doesn't contain %pre scriptlet") assert(spec contains "%post\necho \"post-install\"", "Spec doesn't contain %post scriptlet") + assert(spec contains + """ + |%post + |echo "post-install" + | + |relocateLink() { + | if [ -n "$4" ] ; + | then + | RELOCATED_INSTALL_DIR="$4/$3" + | echo "${1/$2/$RELOCATED_INSTALL_DIR}" + | else + | echo "$1" + | fi + |} + |rm -rf $(relocateLink /etc/rpm-test /usr/share/rpm-test rpm-test $RPM_INSTALL_PREFIX) && ln -s $(relocateLink /usr/share/rpm-test/conf /usr/share/rpm-test rpm-test $RPM_INSTALL_PREFIX) $(relocateLink /etc/rpm-test /usr/share/rpm-test rpm-test $RPM_INSTALL_PREFIX) + |""".stripMargin, "%post scriptlet does not contain relocateLink") + assert(spec contains "%pretrans\necho \"pretrans\"", "Spec doesn't contain %pretrans scriptlet") assert(spec contains "%posttrans\necho \"posttrans\"", "Spec doesn't contain %posttrans scriptlet") assert(spec contains "%preun\necho \"pre-uninstall\"", "Spec doesn't contain %preun scriptlet") assert(spec contains "%postun\necho \"post-uninstall\"", "Spec doesn't contain %postun scriptlet") + assert(spec contains + """ + |%postun + |echo "post-uninstall" + | + |relocateLink() { + | if [ -n "$4" ] ; + | then + | RELOCATED_INSTALL_DIR="$4/$3" + | echo "${1/$2/$RELOCATED_INSTALL_DIR}" + | else + | echo "$1" + | fi + |} + |[ -e /etc/sysconfig/rpm-test ] && . /etc/sysconfig/rpm-test + |rm -rf $(relocateLink /etc/rpm-test /usr/share/rpm-test rpm-test $PACKAGE_PREFIX) + |""".stripMargin, "%postun scriptlet does not contain relocate link") out.log.success("Successfully tested rpm test file") () }