Skip to content

Commit

Permalink
Relocatable rpm symlink fix
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fsat committed Nov 3, 2015
1 parent 34570ac commit 96ab8d0
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,5 +295,4 @@ object JavaServerAppPackaging extends AutoPlugin {
case script => TemplateWriter generateScriptFromString (content + script, replacements)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -249,4 +249,7 @@ object LinuxPlugin extends AutoPlugin {
)
}

final def chdir(installLocation: String, packageName: String): String =
s"$installLocation/$packageName"

}
3 changes: 0 additions & 3 deletions src/main/scala/com/typesafe/sbt/packager/rpm/RpmHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
100 changes: 93 additions & 7 deletions src/main/scala/com/typesafe/sbt/packager/rpm/RpmMetadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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))
Expand All @@ -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.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions src/sbt-test/rpm/scriptlets-rpm/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
()
}
Expand Down

0 comments on commit 96ab8d0

Please sign in to comment.