Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wip/server permissions #174

Merged
merged 3 commits into from Feb 28, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
136 changes: 66 additions & 70 deletions src/main/scala/com/typesafe/sbt/packager/GenericPackageSettings.scala
Expand Up @@ -3,16 +3,16 @@ package packager

import Keys._
import sbt._
import sbt.Keys.{name, mappings, sourceDirectory}
import sbt.Keys.{ name, mappings, sourceDirectory }
import linux.LinuxSymlink
import linux.LinuxPackageMapping

trait GenericPackageSettings
extends linux.LinuxPlugin
with debian.DebianPlugin
with rpm.RpmPlugin
with windows.WindowsPlugin
with universal.UniversalPlugin {
trait GenericPackageSettings
extends linux.LinuxPlugin
with debian.DebianPlugin
with rpm.RpmPlugin
with windows.WindowsPlugin
with universal.UniversalPlugin {

import linux.LinuxPlugin.Users

Expand All @@ -21,10 +21,10 @@ trait GenericPackageSettings
// It is by no means 100% accurate, but should be ok for the simplest cases.
// For advanced users, use the underlying APIs.
// Right now, it's also pretty focused on command line scripts packages.

/**
* Maps linux file format from the universal from the conventions:
*
*
* `<project>/src/linux` files are mapped directly into linux packages.
* `<universal>` files are placed under `/usr/share/<package-name>`
* `<universal>/bin` files are given symlinks in `/usr/bin`
Expand All @@ -40,114 +40,110 @@ trait GenericPackageSettings
case (file, name) => (name contains "man/") && (name endsWith ".1")
}
val compressedManPages =
for((file, name) <- manPages)
yield file -> (name + ".gz")
for ((file, name) <- manPages)
yield file -> (name + ".gz")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT of using Scalariform, which although not my favorite style, will help prevent all of us from whitespace-line changing each other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm already using it through Eclipse (at least that's what the scalariform page says). However an integration in the build with sbt-scalariform would be helpful as everbody uses another IDE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

val (configFiles, remaining) = nonManPages partition {
case (file, name) => (name contains "etc/") || (name contains "conf/")
}
def packageMappingWithRename(mappings: (File, String)*): LinuxPackageMapping = {
val renamed =
for((file, name) <- mappings)
yield file -> rename(name)
packageMapping(renamed:_*)
for ((file, name) <- mappings)
yield file -> rename(name)
packageMapping(renamed: _*)
}

Seq(
packageMappingWithRename((binaries ++ directories):_*) withUser user withGroup group withPerms "0755",
packageMappingWithRename(compressedManPages:_*).gzipped withUser user withGroup group withPerms "0644",
packageMappingWithRename(configFiles:_*) withConfig() withUser user withGroup group withPerms "0644",
packageMappingWithRename(remaining:_*) withUser user withGroup group withPerms "0644"
)
packageMappingWithRename((binaries ++ directories): _*) withUser user withGroup group withPerms "0755",
packageMappingWithRename(compressedManPages: _*).gzipped withUser user withGroup group withPerms "0644",
packageMappingWithRename(configFiles: _*) withConfig () withUser user withGroup group withPerms "0644",
packageMappingWithRename(remaining: _*) withUser user withGroup group withPerms "0644")
}

def mapGenericFilesToLinux: Seq[Setting[_]] = Seq(

// Default place to install code.
defaultLinuxInstallLocation := "/usr/share",
defaultLinuxLogsLocation := "/var/log",
defaultLinuxConfigLocation := "/etc",

// First we look at the src/linux files
linuxPackageMappings <++= (sourceDirectory in Linux, appUser in Linux, appGroup in Linux) map { (dir, user, group) =>
mapGenericMappingsToLinux((dir.*** --- dir) x relativeTo(dir), user, group)(identity)
linuxPackageMappings <++= (sourceDirectory in Linux, daemonUser in Linux, daemonGroup in Linux) map { (dir, user, group) =>
mapGenericMappingsToLinux(MappingsHelper contentOf dir, user, group)(identity)
},
// Now we look at the src/universal files.
linuxPackageMappings <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation, appUser in Linux, appGroup in Linux) map {
linuxPackageMappings <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation, daemonUser in Linux, daemonGroup in Linux) map {
(pkg, mappings, installLocation, user, group) =>
// TODO - More windows filters...
def isWindowsFile(f: (File, String)): Boolean =
f._2 endsWith ".bat"
mapGenericMappingsToLinux(mappings filterNot isWindowsFile, user, group) { name =>
installLocation + "/" + pkg + "/" + name
}
// TODO - More windows filters...
def isWindowsFile(f: (File, String)): Boolean =
f._2 endsWith ".bat"

mapGenericMappingsToLinux(mappings filterNot isWindowsFile, user, group) { name =>
installLocation + "/" + pkg + "/" + name
}
},
// Now we generate symlinks.
linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation) map { (pkg, mappings, installLocation) =>
for {
(file, name) <- mappings
if !file.isDirectory
if name startsWith "bin/"
if !(name endsWith ".bat") // IGNORE windows-y things.
} yield LinuxSymlink("/usr/" + name, installLocation+"/"+pkg+"/"+name)
for {
(file, name) <- mappings
if !file.isDirectory
if name startsWith "bin/"
if !(name endsWith ".bat") // IGNORE windows-y things.
} yield LinuxSymlink("/usr/" + name, installLocation + "/" + pkg + "/" + name)
},
// Map configuration files
linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation) map { (pkg, mappings, installLocation) =>
linuxPackageSymlinks <++= (normalizedName in Universal, mappings in Universal, defaultLinuxInstallLocation, defaultLinuxConfigLocation)
map { (pkg, mappings, installLocation, configLocation) =>
val needsConfLink =
mappings exists { case (file, name) =>
(name startsWith "conf/") && !file.isDirectory
mappings exists {
case (file, name) =>
(name startsWith "conf/") && !file.isDirectory
}
if(needsConfLink) Seq(LinuxSymlink(
link="/etc/" + pkg,
destination=installLocation+"/"+pkg+"/conf"))
if (needsConfLink) Seq(LinuxSymlink(
link = configLocation + "/" + pkg,
destination = installLocation + "/" + pkg + "/conf"))
else Seq.empty
}
)

})

def mapGenericFilesToWindows: Seq[Setting[_]] = Seq(
mappings in Windows <<= mappings in Universal,
wixFeatures <<= (name in Windows, mappings in Windows) map makeWindowsFeatures
)
wixFeatures <<= (name in Windows, mappings in Windows) map makeWindowsFeatures)
// TODO select main script! Filter Config links!
def makeWindowsFeatures(name: String, mappings: Seq[(File, String)]): Seq[windows.WindowsFeature] = {
import windows._

val files =
for {
(file, name) <- mappings
if !file.isDirectory
} yield ComponentFile(name, editable = (name startsWith "conf"))
val corePackage =
WindowsFeature(
id=WixHelper.cleanStringForId(name + "_core").takeRight(38), // Must be no longer
title=name,
desc="All core files.",
absent="disallow",
components = files
)
id = WixHelper.cleanStringForId(name + "_core").takeRight(38), // Must be no longer
title = name,
desc = "All core files.",
absent = "disallow",
components = files)
// TODO - Detect bat files to add paths...
val addBinToPath =
// TODO - we may have issues here...
WindowsFeature(
id="AddBinToPath",
title="Update Enviornment Variables",
desc="Update PATH environment variables (requires restart).",
components = Seq(AddDirectoryToPath("bin"))
)
id = "AddBinToPath",
title = "Update Enviornment Variables",
desc = "Update PATH environment variables (requires restart).",
components = Seq(AddDirectoryToPath("bin")))
val configLinks = for {
(file, name) <- mappings
if !file.isDirectory
if name startsWith "conf/"
} yield name.replaceAll("//", "/").stripSuffix("/").stripSuffix("/")
(file, name) <- mappings
if !file.isDirectory
if name startsWith "conf/"
} yield name.replaceAll("//", "/").stripSuffix("/").stripSuffix("/")
val menuLinks =
WindowsFeature(
id="AddConfigLinks",
title="Configuration start menu links",
desc="Adds start menu shortcuts to edit configuration files.",
components = Seq(AddShortCuts(configLinks))
)
id = "AddConfigLinks",
title = "Configuration start menu links",
desc = "Adds start menu shortcuts to edit configuration files.",
components = Seq(AddShortCuts(configLinks)))
// TODO - Add feature for shortcuts to binary scripts.
Seq(corePackage, addBinToPath, menuLinks)
}



}
1 change: 1 addition & 0 deletions src/main/scala/com/typesafe/sbt/packager/Keys.scala
Expand Up @@ -31,4 +31,5 @@ object Keys extends linux.Keys
| """.stripMargin)
val defaultLinuxInstallLocation = SettingKey[String]("defaultLinuxInstallLocation", "The location where we will install generic linux packages.")
val defaultLinuxLogsLocation = SettingKey[String]("defaultLinuxLogsLocation", "The location where application logs will be stored.")
val defaultLinuxConfigLocation = SettingKey[String]("defaultLinuxConfigLocation", "The location where application config files will be stored")
}
Expand Up @@ -6,7 +6,7 @@ import Keys._
import sbt._
import sbt.Project.Initialize
import sbt.Keys.{ mappings, target, name, mainClass, normalizedName, sourceDirectory }
import com.typesafe.sbt.packager.linux.{LinuxFileMetaData, LinuxPackageMapping}
import com.typesafe.sbt.packager.linux.{ LinuxFileMetaData, LinuxPackageMapping }
import SbtNativePackager._

/**
Expand Down Expand Up @@ -64,9 +64,9 @@ object JavaAppPackaging {
s <- script.toSeq
} yield s -> ("bin/" + name + ".bat")
},
linuxPackageMappings in Debian <+= (normalizedName, defaultLinuxInstallLocation, target in Debian, appUser in Linux, appGroup in Linux) map {
linuxPackageMappings in Debian <+= (normalizedName, defaultLinuxInstallLocation, target in Debian, daemonUser in Linux, daemonGroup in Linux) map {
(name, installLocation, target, user, group) =>
// create empty var/log directory
// create empty var/log directory
val d = target / installLocation
d.mkdirs()
LinuxPackageMapping(Seq(d -> (installLocation + "/" + name)), LinuxFileMetaData(user, group))
Expand All @@ -86,8 +86,8 @@ object JavaAppPackaging {
if (defines.isEmpty) None
else {
val defaultTemplateLocation = sourceDir / "templates" / "bash-template"
val scriptBits =
if(defaultTemplateLocation.exists) JavaAppBashScript.generateScript(defines, defaultTemplateLocation.toURI.toURL)
val scriptBits =
if (defaultTemplateLocation.exists) JavaAppBashScript.generateScript(defines, defaultTemplateLocation.toURI.toURL)
else JavaAppBashScript.generateScript(defines)
val script = tmpDir / "tmp" / "bin" / name
IO.write(script, scriptBits)
Expand All @@ -100,8 +100,8 @@ object JavaAppPackaging {
if (replacements.isEmpty) None
else {
val defaultTemplateLocation = sourceDir / "templates" / "bat-template"
val scriptBits =
if(defaultTemplateLocation.exists) JavaAppBatScript.generateScript(replacements, defaultTemplateLocation.toURI.toURL)
val scriptBits =
if (defaultTemplateLocation.exists) JavaAppBatScript.generateScript(replacements, defaultTemplateLocation.toURI.toURL)
else JavaAppBatScript.generateScript(replacements)
val script = tmpDir / "tmp" / "bin" / (name + ".bat")
IO.write(script, scriptBits)
Expand Down
Expand Up @@ -89,6 +89,7 @@ object JavaAppStartScript {
chdir: String,
appName: String,
daemonUser: String,
daemonGroup: String,
retries: Int = 0,
retryTimeout: Int = 60): Seq[(String, String)] =
Seq(
Expand All @@ -99,7 +100,8 @@ object JavaAppStartScript {
"retries" -> retries.toString,
"retryTimeout" -> retryTimeout.toString,
"app_name" -> appName,
"daemon_user" -> daemonUser)
"daemon_user" -> daemonUser,
"daemon_group" -> daemonGroup)
}

object ServerLoader extends Enumeration {
Expand Down
Expand Up @@ -29,12 +29,11 @@ object JavaServerAppPackaging {
def debianSettings: Seq[Setting[_]] =
Seq(
serverLoading := Upstart,
daemonUser <<= appUser in Linux,
// This one is begging for sbt 0.13 syntax...
debianScriptReplacements <<= (
maintainer in Debian, packageSummary in Debian, serverLoading in Debian, daemonUser in Debian, normalizedName,
maintainer in Debian, packageSummary in Debian, serverLoading in Debian, daemonUser in Linux, daemonGroup in Linux, normalizedName,
sbt.Keys.version, defaultLinuxInstallLocation)
map { (author, descr, loader, daemonUser, name, version, installLocation) =>
map { (author, descr, loader, daemonUser, daemonGroup, name, version, installLocation) =>
val appDir = installLocation + "/" + name

JavaAppStartScript.makeReplacements(
Expand All @@ -43,7 +42,8 @@ object JavaServerAppPackaging {
execScript = name,
chdir = appDir,
appName = name,
daemonUser = daemonUser)
daemonUser = daemonUser,
daemonGroup = daemonGroup)
},
// TODO - Default locations shouldn't be so hacky.

Expand All @@ -55,15 +55,15 @@ object JavaServerAppPackaging {
map { (tmpDir, loader, replacements, template) =>
makeDebianMaintainerScript(JavaAppStartScript.startScript, Some(template))(tmpDir, loader, replacements)
},
linuxPackageMappings in Debian <++= (debianMakeStartScript, normalizedName, serverLoading in Debian, appUser in Linux, appGroup in Linux)
map { (script, name, loader, owner, ownerGroup) =>
linuxPackageMappings in Debian <++= (debianMakeStartScript, normalizedName, serverLoading in Debian)
map { (script, name, loader) =>
val (path, permissions) = loader match {
case Upstart => ("/etc/init/" + name + ".conf", "0644")
case SystemV => ("/etc/init.d/" + name, "0755")
}
for {
s <- script.toSeq
} yield LinuxPackageMapping(Seq(s -> path), LinuxFileMetaData(owner, ownerGroup, permissions, "true"))
} yield LinuxPackageMapping(Seq(s -> path), LinuxFileMetaData(Users.Root, Users.Root, permissions, "true"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.... someone did request the ability to modify these before though, right?

I think the direction we're taking is correct, but I'm still curious how this pans out with our audience. You can always drop down and specify mappings directly. I think you may be right we need to provide a convenient way to filter mappings successfully after the fact.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a long discussion in #170 which I summed up in this #174 pull request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I guess I'm not fully convinced this is the right path, but it's the best option right now, most likely. I think if we open one of your two suggested options, the point becomes moot as well. This will be a documentation problem, though :)

},

// === etc config mapping ===
Expand All @@ -75,13 +75,13 @@ object JavaServerAppPackaging {
},
debianMakeEtcDefault <<= (normalizedName, target in Universal, linuxEtcDefaultTemplate in Debian, debianScriptReplacements)
map makeEtcDefaultScript,
linuxPackageMappings in Debian <++= (debianMakeEtcDefault, normalizedName, appUser in Linux, appGroup in Linux) map { (conf, name, owner, ownerGroup) =>
conf.map(c => LinuxPackageMapping(Seq(c -> ("/etc/default/" + name)), LinuxFileMetaData(owner, ownerGroup)).withConfig()).toSeq
linuxPackageMappings in Debian <++= (debianMakeEtcDefault, normalizedName) map { (conf, name) =>
conf.map(c => LinuxPackageMapping(Seq(c -> ("/etc/default/" + name)), LinuxFileMetaData(Users.Root, Users.Root)).withConfig()).toSeq
},
// TODO should we specify daemonGroup in configs?

// === logging directory mapping ===
linuxPackageMappings in Debian <+= (normalizedName, defaultLinuxLogsLocation, target in Debian, appUser in Linux, appGroup in Linux) map {
linuxPackageMappings in Debian <+= (normalizedName, defaultLinuxLogsLocation, target in Debian, daemonUser in Linux, daemonGroup in Linux) map {
(name, logsDir, target, user, group) =>
// create empty var/log directory
val d = target / logsDir
Expand Down
5 changes: 0 additions & 5 deletions src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala
Expand Up @@ -83,12 +83,7 @@ object Keys extends DebianKeys {
def target = sbt.Keys.target
def streams = sbt.Keys.streams

// file ownership
def appUser = linux.Keys.appUser
def appGroup = linux.Keys.appGroup

//init script parameters
def daemonUser = linux.Keys.daemonUser
def serverLoading = linux.Keys.serverLoading

val debianPackageInstallSize = TaskKey[Long]("debian-installed-size")
Expand Down
7 changes: 3 additions & 4 deletions src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala
Expand Up @@ -11,14 +11,13 @@ trait Keys {
val packageSummary = SettingKey[String]("package-summary", "Summary of the contents of a linux package.")
val packageDescription = SettingKey[String]("package-description", "The description of the package. Used when searching.")
val maintainer = SettingKey[String]("maintainer", "The name/email address of a maintainer for the native package.")
val appUser = SettingKey[String]("app-user", "The owner of the files in the package")
val appGroup = SettingKey[String]("app-group", "The group owner of the files in the package")
val daemonUser = SettingKey[String]("daemon-user", "User to start application daemon")
val daemonGroup = SettingKey[String]("daemon-group", "Group to start application daemon")
val serverLoading = SettingKey[ServerLoader]("server-loader", "Loading system to be used for application start script")
val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.")
val linuxPackageSymlinks = TaskKey[Seq[LinuxSymlink]]("linux-package-symlinks", "Symlinks we should produce in the underlying package.")
val generateManPages = TaskKey[Unit]("generate-man-pages", "Shows all the man files in the current project")
val generateManPages = TaskKey[Unit]("generate-man-pages", "Shows all the man files in the current project")

val linuxStartScriptTemplate = TaskKey[URL]("linuxStartScriptTemplate", "The location of the template start script file we use for debian (upstart or init.d")
val linuxEtcDefaultTemplate = TaskKey[URL]("linuxEtcDefaultTemplate", "The location of the /etc/default/<pkg> template script.")
}
Expand Down
Expand Up @@ -28,7 +28,8 @@ trait LinuxPlugin extends Plugin {
},
packageSummary in Linux <<= packageSummary,
packageDescription in Linux <<= packageDescription,
appUser <<= normalizedName, appGroup <<= appUser in Linux)
daemonUser in Linux <<= normalizedName,
daemonGroup <<= daemonUser in Linux)

/** DSL for packaging files into .deb */
def packageMapping(files: (File, String)*) = LinuxPackageMapping(files)
Expand Down