Skip to content

Commit e4ff54a

Browse files
committed
Add read/write for Dockerfiles
1 parent ab8940d commit e4ff54a

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,7 @@ export(get_package_manager)
4343
export(has_instruction)
4444
export(is_dockerfile)
4545
export(is_dockerignore)
46+
export(read_dockerfile)
4647
export(read_dockerignore)
48+
export(write_dockerfile)
4749
export(write_dockerignore)

R/dockerfile-io.R

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#' Read a Dockerfile from a file
2+
#'
3+
#' @param file Path to Dockerfile
4+
#' @return A dockerfile object
5+
#' @export
6+
read_dockerfile <- function(file) {
7+
if (!file.exists(file)) {
8+
cli::cli_abort("File not found: {file}")
9+
}
10+
11+
# Read lines from file
12+
lines <- readLines(file)
13+
14+
# Create a new dockerfile object
15+
df <- dockerfile()
16+
17+
# Process continuation lines
18+
i <- 1
19+
while (i <= length(lines)) {
20+
line <- lines[i]
21+
22+
# Skip empty lines and comments
23+
if (grepl("^\\s*$", line) || grepl("^\\s*#", line)) {
24+
i <- i + 1
25+
next
26+
}
27+
28+
# Check for continuation character at the end
29+
while (grepl("\\\\\\s*$", line) && i < length(lines)) {
30+
# Remove continuation character
31+
line <- sub("\\\\\\s*$", " ", line)
32+
# Add next line
33+
i <- i + 1
34+
line <- paste0(line, lines[i])
35+
}
36+
37+
# Add processed line to dockerfile
38+
df$lines <- c(df$lines, line)
39+
40+
# Update metadata
41+
if (grepl("^FROM ", line, ignore.case = TRUE)) {
42+
base_image <- sub("^FROM\\s+([^\\s]+).*$", "\\1", line, ignore.case = TRUE)
43+
df$metadata$base_image <- base_image
44+
df$metadata$package_manager <- get_package_manager(base_image)
45+
46+
# Try to extract R version from rocker images
47+
if (grepl("^rocker/r-ver:", base_image)) {
48+
r_ver <- sub("^rocker/r-ver:", "", base_image)
49+
df$metadata$r_version <- r_ver
50+
}
51+
}
52+
53+
i <- i + 1
54+
}
55+
56+
df
57+
}
58+
59+
#' Write a dockerfile to a file
60+
#'
61+
#' @param dockerfile A dockerfile object
62+
#' @param file Output file path (default: "Dockerfile")
63+
#' @param multiline Logical indicating if long RUN commands should be split (default: TRUE)
64+
#' @return Invisible dockerfile object
65+
#' @export
66+
write_dockerfile <- function(dockerfile, file = "Dockerfile", multiline = TRUE) {
67+
check_dockerfile(dockerfile)
68+
69+
if (multiline) {
70+
# Process lines for better formatting
71+
formatted_lines <- character(length(dockerfile$lines))
72+
73+
for (i in seq_along(dockerfile$lines)) {
74+
line <- dockerfile$lines[i]
75+
76+
# Check if it's a RUN command with && that could be split
77+
if (grepl("^RUN ", line) && nchar(line) > 80 && grepl(" && ", line)) {
78+
parts <- strsplit(line, " && ")[[1]]
79+
formatted_line <- paste0(
80+
"RUN ", parts[1], " && \\\n",
81+
paste0(" ", parts[-1], collapse = " && \\\n")
82+
)
83+
formatted_lines[i] <- formatted_line
84+
} else {
85+
formatted_lines[i] <- line
86+
}
87+
}
88+
89+
# Write to file
90+
writeLines(formatted_lines, file)
91+
} else {
92+
# Write directly without formatting
93+
writeLines(dockerfile$lines, file)
94+
}
95+
96+
cli::cli_alert_success("Dockerfile written to {file}")
97+
invisible(dockerfile)
98+
}

man/print.dockerfile.Rd

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/read_dockerfile.Rd

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/write_dockerfile.Rd

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Test read_dockerfile() ----
2+
test_that("read_dockerfile(): reads Dockerfile correctly", {
3+
# Create a temporary Dockerfile for testing
4+
dockerfile_path <- tempfile(fileext = "")
5+
writeLines(c(
6+
"FROM ubuntu:24.10",
7+
"RUN apt-get update && \\",
8+
" apt-get install -y curl",
9+
"WORKDIR /app",
10+
"COPY . /app/",
11+
"CMD [\"bash\"]"
12+
), dockerfile_path)
13+
14+
# Read the Dockerfile
15+
df <- read_dockerfile(dockerfile_path)
16+
expect_true(is_dockerfile(df))
17+
expect_equal(length(df$lines), 5) # 5 lines after processing continuations
18+
expect_equal(df$metadata$base_image, "ubuntu:24.10")
19+
expect_equal(df$metadata$package_manager, "apt")
20+
21+
# Test error for missing file
22+
expect_error(read_dockerfile("nonexistent_dockerfile"), "File not found")
23+
24+
# Clean up
25+
unlink(dockerfile_path)
26+
})
27+
28+
# Test write_dockerfile() ----
29+
test_that("write_dockerfile(): writes Dockerfile correctly", {
30+
# Create a dockerfile
31+
df <- dockerfile() |>
32+
dfi_from("ubuntu:24.10") |>
33+
dfi_run("apt-get update && apt-get install -y curl") |>
34+
dfi_workdir("/app") |>
35+
dfi_cmd("bash")
36+
37+
# Write to temporary file
38+
dockerfile_path <- tempfile(fileext = "")
39+
expect_message(
40+
write_dockerfile(df, dockerfile_path)
41+
)
42+
43+
# Check that file exists
44+
expect_true(file.exists(dockerfile_path))
45+
46+
# Read the file back
47+
lines <- readLines(dockerfile_path)
48+
expect_true(any(grepl("^FROM ubuntu:24.10", lines)))
49+
expect_true(any(grepl("^RUN apt-get update", lines)))
50+
expect_true(any(grepl("^WORKDIR /app", lines)))
51+
expect_true(any(grepl("^CMD bash", lines)))
52+
53+
# Test multiline formatting
54+
multiline_df <- dockerfile() |>
55+
dfi_from("ubuntu:24.10") |>
56+
dfi_run("apt-get update && apt-get install -y curl && apt-get clean && rm -rf /var/lib/apt/lists/*")
57+
58+
multiline_path <- tempfile(fileext = "")
59+
expect_message(
60+
write_dockerfile(multiline_df, multiline_path, multiline = TRUE)
61+
)
62+
63+
# Check that the long RUN command was split
64+
multilines <- readLines(multiline_path)
65+
expect_true(any(grepl("\\\\$", multilines))) # Check for continuation characters
66+
67+
# Clean up
68+
unlink(dockerfile_path)
69+
unlink(multiline_path)
70+
})

0 commit comments

Comments
 (0)