Skip to content

Commit

Permalink
add create Dockerfile from sessionInfo object in sessionInfo.Rdata
Browse files Browse the repository at this point in the history
also some code restructuring and extraction of bespoke dependency installers
  • Loading branch information
nuest committed Mar 23, 2018
1 parent 59b2f7a commit 34b1756
Show file tree
Hide file tree
Showing 24 changed files with 554 additions and 396 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Collate:
'containerit.R'
'defaults.R'
'dockerfile.R'
'package-installation-bespoke.R'
'package-installation-methods.R'
'sessionInfo-localbuild-methods.R'
'utility-functions.R'
Expand Down
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export(dockerfile)
export(getGitHubRef)
export(getImageForVersion)
export(getRVersionTag)
export(getSessionInfoFromRdata)
export(parseFrom)
exportClasses(Add)
exportClasses(Arg)
Expand Down
2 changes: 1 addition & 1 deletion R/Class-Run.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ setClass("Run_shell",
# or ["param1","param2"] (for CMD as default parameters to ENTRYPOINT)

commands <- methods::slot(obj, "commands")
string <- paste(commands, collapse = " \\\n && ")
string <- paste(commands, collapse = " \\\n && ")
return(string)
}

Expand Down
129 changes: 63 additions & 66 deletions R/dockerfile.R
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@
#' This executes the whole file to obtain a complete \code{sessionInfo} object, see section "Based on \code{sessionInfo}", and copies required files and documents into the container.
#'
#' @param from The source of the information to construct the Dockerfile. Can be a \code{sessionInfo} object, a path to a file, or the path to a workspace). If \code{NULL} then no automatic derivation of dependencies happens.
#' @param image (\linkS4class{From}-object or character) Specifes the image that shall be used for the Docker container (\code{FROM} instruction).
#' By default, the image is determinded from the given session. Alternatively, use \code{getImageForVersion(..)} to get an existing image for a manually defined version of R, matching the version with tags from the base image rocker/r-ver (see details about the rocker/r-ver at \url{'https://hub.docker.com/r/rocker/r-ver/'}). Or provide a correct image name yourself.
#' @param maintainer Specify the maintainer of the dockerfile. See documentation at \url{'https://docs.docker.com/engine/reference/builder/#maintainer'}. Defaults to \code{Sys.info()[["user"]]}. Can be removed with \code{NULL}.
#' @param save_image When TRUE, it calls \link[base]{save.image} and include the resulting .RData in the container's working directory.
#' Alternatively, you can pass a list of objects to be saved, which may also include arguments to be passed down to \code{save}. E.g. save_image = list("object1", "object2", file = "path/in/wd/filename.RData").
#' \code{save} will be called with default arguments file = ".RData" and envir = .GlobalEnv
#' @param maintainer Specify the maintainer of the dockerfile. See documentation at \url{'https://docs.docker.com/engine/reference/builder/#maintainer'}. Defaults to \code{Sys.info()[["user"]]}. Can be removed with \code{NULL}.
#' @param r_version (character) optionally specify the R version that should run inside the container. By default, the R version from the given sessioninfo is used (if applicable) or the version of the currently running R instance
#' @param image (From-object or character) optionally specify the image that shall be used for the Docker container (FROM-statement)
#' By default, the image is determinded from the given r_version, while the version is matched with tags from the base image rocker/r-ver
#' see details about the rocker/r-ver at \url{'https://hub.docker.com/r/rocker/r-ver/'}
#' @param env optionally specify environment variables to be included in the image. See documentation: \url{'https://docs.docker.com/engine/reference/builder/#env}
#' @param soft (boolean) Whether to include soft dependencies when system dependencies are installed, default is no.
#' @param offline (boolean) Whether to use an online database to detect system dependencies or use local package information (slower!), default is no.
Expand All @@ -47,6 +45,7 @@
#' @param vanilla Whether to use an empty vanilla session when packaging scripts and markdown files (equivalent to \code{R --vanilla})
#' @param silent Whether or not to print information during execution
#' @param versioned_libs [EXPERIMENTAL] Whether it shall be attempted to match versions of linked external libraries
#' @param versioned_packages [EXPERIMENTAL] Whether it shall be attempted to match versions of R packages
#'
#' @return An object of class Dockerfile
#'
Expand All @@ -59,14 +58,13 @@
#' print(dockerfile)
#'
dockerfile <- function(from = utils::sessionInfo(),
save_image = FALSE,
image = getImageForVersion(getRVersionTag(from)),
maintainer = Sys.info()[["user"]],
r_version = getRVersionTag(from),
image = getImageForVersion(r_version),
save_image = FALSE,
env = list(generator = paste("containerit", utils::packageVersion("containerit"))),
soft = FALSE,
offline = FALSE,
copy = "script",
copy = NA, # "script",
# nolint start
container_workdir = "/payload/",
# nolint end
Expand All @@ -75,19 +73,20 @@ dockerfile <- function(from = utils::sessionInfo(),
add_self = FALSE,
vanilla = TRUE,
silent = FALSE,
versioned_libs = FALSE) {
versioned_libs = FALSE,
versioned_packages = FALSE) {
if (silent) {
invisible(futile.logger::flog.threshold(futile.logger::WARN))
}

futile.logger::flog.debug("Creating a new Dockerfile from '%s'", from)
.dockerfile <- NA
.originalFrom <- class(from)

#parse From-object from string if necessary
if (is.character(image)) {
image <- parseFrom(image)
}
futile.logger::flog.debug("Creating a new Dockerfile from '%s' with base image %s", from, toString(image))

if (is.character(maintainer)) {
.label <- Label_Maintainer(maintainer)
Expand Down Expand Up @@ -150,12 +149,13 @@ dockerfile <- function(from = utils::sessionInfo(),
futile.logger::flog.debug("Creating from sessionInfo object")
.dockerfile <-
dockerfileFromSession(
session = from,
.dockerfile = .dockerfile,
session = from,
soft = soft,
offline = offline,
add_self = add_self,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir
)
} else if (inherits(x = from, "character")) {
Expand All @@ -175,10 +175,11 @@ dockerfile <- function(from = utils::sessionInfo(),
vanilla = vanilla,
silent = silent,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir
)
} else if (file.exists(from)) {
futile.logger::flog.debug("'%s' is a file")
futile.logger::flog.debug("'%s' is a file", from)
.originalFrom <- from
.dockerfile <-
dockerfileFromFile(
Expand All @@ -191,29 +192,27 @@ dockerfile <- function(from = utils::sessionInfo(),
vanilla = vanilla,
silent = silent,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir
)
} else {
stop("Unsupported string for 'from' argument (not a file, not a directory): ", from)
}
} else if (is.expression(from) ||
(is.list(from) && all(sapply(from, is.expression)))) {
futile.logger::flog.debug("Creating from expession: %s", toString(from))

.sessionInfo <-
clean_session(expr = from,
slave = silent,
vanilla = vanilla)
.dockerfile <-
dockerfileFromSession(
session = .sessionInfo,
.dockerfile = .dockerfile,
soft = soft,
offline = offline,
add_self = add_self,
versioned_libs = versioned_libs,
workdir = workdir
)
futile.logger::flog.debug("Creating from expession: '%s' with a clean session", toString(from))

.sessionInfo <- clean_session(expr = from,
slave = silent,
vanilla = vanilla)
.dockerfile <- dockerfileFromSession(.dockerfile = .dockerfile,
session = .sessionInfo,
soft = soft,
offline = offline,
add_self = add_self,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir)
} else {
stop("Unsupported 'from': ", class(from), " ", from)
}
Expand Down Expand Up @@ -244,6 +243,7 @@ dockerfileFromSession <- function(session,
offline,
add_self,
versioned_libs,
versioned_packages,
workdir) {
futile.logger::flog.debug("Creating from sessionInfo")

Expand All @@ -259,20 +259,21 @@ dockerfileFromSession <- function(session,
image_name = .dockerfile@image@image
if (image_name %in% .debian_images) {
platform = .debian_platform
futile.logger::flog.debug("Found image %s in list of debian images.")
futile.logger::flog.debug("Found image %s in list of Debian images", image_name)
}
futile.logger::flog.debug("Detected platform %s", platform)
futile.logger::flog.debug("Detected platform: %s", platform)

.dockerfile <-
.create_run_install(
.dockerfile = .dockerfile,
pkgs = pkgs,
platform = platform,
soft = soft,
offline = offline,
versioned_libs = versioned_libs,
workdir = workdir # will be added as last instruction
)
.dockerfile <- .create_run_install(
dockerfile = .dockerfile,
pkgs = pkgs,
platform = platform,
soft = soft,
offline = offline,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages)

# after all installation is done, set the workdir
addInstruction(.dockerfile) <- workdir

return(.dockerfile)
}
Expand All @@ -287,11 +288,11 @@ dockerfileFromFile <-
vanilla,
silent,
versioned_libs,
versioned_packages,
workdir) {
futile.logger::flog.debug("Creating from file")
#################################################

# prepare context ( = working directory) and normalize paths:
#################################################
context = normalizePath(getwd())
file = normalizePath(file)

Expand All @@ -304,9 +305,7 @@ dockerfileFromFile <-
# make sure that the path is relative to context
rel_path <- .makeRelative(file, context)

####################################################
# execute script / markdowns and obtain sessioninfo
#####################################################
# execute script / markdowns or read Rdata file to obtain sessioninfo
if (stringr::str_detect(file, ".R$")) {
futile.logger::flog.info("Executing R script file in %s locally.", rel_path)
sessionInfo <-
Expand All @@ -316,7 +315,7 @@ dockerfileFromFile <-
slave = silent,
echo = !silent
)
} else if (stringr::str_detect(file, ".Rmd$")) {
} else if (stringr::str_detect(file, ".Rmd$")) {
futile.logger::flog.info("Processing the given file %s locally using rmarkdown::render(...)", rel_path)
sessionInfo <-
obtain_localSessionInfo(
Expand All @@ -325,13 +324,13 @@ dockerfileFromFile <-
slave = silent,
echo = !silent
)
} else if (stringr::str_detect(file, ".Rdata$")) {
sessionInfo <- getSessionInfoFromRdata(file)
} else{
futile.logger::flog.info("The supplied file %s has no known extension. containerit will handle it as an R script for packaging.", rel_path)
}

####################################################
# append system dependencies and package installation instructions
####################################################
.dockerfile <-
dockerfileFromSession(
session = sessionInfo,
Expand All @@ -340,11 +339,11 @@ dockerfileFromFile <-
offline = offline,
add_self = add_self,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir
)

## working directory must be set before. Now add copy instructions
####################################################
if (!is.null(copy) && !is.na(copy)) {
copy = unlist(copy)
if (!is.character(copy)) {
Expand Down Expand Up @@ -400,6 +399,7 @@ dockerfileFromWorkspace <-
vanilla,
silent,
versioned_libs,
versioned_packages,
workdir) {
futile.logger::flog.debug("Creating from workspace directory")
target_file <- NULL #file to be packaged
Expand Down Expand Up @@ -453,13 +453,13 @@ dockerfileFromWorkspace <-
vanilla = vanilla,
silent = silent,
versioned_libs = versioned_libs,
versioned_packages = versioned_packages,
workdir = workdir
)
return(.df)
}



#' getImageForVersion-method
#'
#' Get a suitable Rocker image based on the R version.
Expand All @@ -483,7 +483,7 @@ getImageForVersion <- function(r_version, nearest = TRUE) {
image <- From(.rocker_images[["versioned"]], tag = r_version)

closestMatch <- function(version, versions) {
if(version %in% versions) return(version);
if (version %in% versions) return(version);

factors <- list(major = 1000000, minor = 1000, patch = 1)

Expand All @@ -506,23 +506,21 @@ getImageForVersion <- function(r_version, nearest = TRUE) {
}

if (!r_version %in% tags) {
if(nearest) {
if (nearest) {
# get numeric versions with all parts (maj.min.minor), i.e. two dots
numeric_tags <- tags[which(grepl("\\d.\\d.\\d", tags))]
closest <- as.character(closestMatch(r_version, numeric_tags))
image <- From(.rocker_images[["versioned"]], tag = closest)

warning(
"No Docker image found for the given R version, returning closest match: ",
closest,
" Existing tags (list only available when online): ",
paste(tags, collapse = " ")
warning("No Docker image found for the given R version, returning closest match: ",
closest,
" Existing tags (list only available when online): ",
paste(tags, collapse = " ")
)
} else {
warning(
"No Docker image found for the given R version, returning input. ",
"Existing tags (list only available when online): ",
paste(tags, collapse = " ")
warning("No Docker image found for the given R version, returning input. ",
"Existing tags (list only available when online): ",
paste(tags, collapse = " ")
)
}
}
Expand All @@ -531,10 +529,9 @@ getImageForVersion <- function(r_version, nearest = TRUE) {
}

.tagsfromRemoteImage <- function(image) {
urlstr <-
paste0("https://registry.hub.docker.com/v2/repositories/",
image,
"/tags/?page_size=9999")
urlstr <- paste0("https://registry.hub.docker.com/v2/repositories/",
image,
"/tags/?page_size=9999")

tryCatch({
con <- url(urlstr)
Expand Down
Loading

0 comments on commit 34b1756

Please sign in to comment.