context("docker client: containers") test_that("create", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) expect_is(x, "docker_container") expect_is(x, "stevedore_object") expect_equal(x$name(), nm) expect_equal(x$id(), x$inspect()$id) expect_equal(x$inspect()$name, paste0("/", nm)) expect_identical(x$reload(), x) expect_null(x$remove()) e <- get_error(x$inspect()) expect_is(e, "docker_error") expect_equal(e$code, 404L) expect_equal(e$endpoint, "container_inspect") expect_equal(e$reason, "no such container") }) test_that("create, using image", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") img <- d$image$get("hello-world") x <- d$container$create(img, name = nm) expect_equal(x$image()$id(), img$id()) x$remove() }) test_that("get", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x1 <- d$container$create("hello-world", name = nm) x2 <- d$container$get(nm) expect_identical(x1$inspect(FALSE), x2$inspect(FALSE)) d$container$remove(nm) e <- get_error(d$container$get(nm)) expect_is(e, "docker_error") expect_equal(e$code, 404L) }) test_that("list", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) cl <- d$container$list(all = TRUE) expect_is(cl, "data.frame") expect_true("names" %in% names(cl)) expect_true(list(nm) %in% cl$names) expect_true(nm %in% cl$name) x$remove() }) test_that("list with arguments", { d <- test_docker_client() cl <- d$container$list() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) ## First, the easy arguments: cl <- d$container$list(all = TRUE, limit = 10L, size = TRUE) i <- match(nm, cl$name) expect_false(is.na(i)) expect_false(is.na(cl$size_root_fs[i])) ## Then the filters f1 <- c(status = "created") f2 <- list(status = "exited") cl1 <- d$container$list(all = TRUE, limit = 10L, size = TRUE, filters = f1) cl2 <- d$container$list(all = TRUE, limit = 10L, size = TRUE, filters = f2) expect_true(nm %in% cl1$name) expect_false(nm %in% cl2$name) expect_identical(x$start(), x) x$wait() cl1 <- d$container$list(all = TRUE, limit = 10L, size = TRUE, filters = f1) cl2 <- d$container$list(all = TRUE, limit = 10L, size = TRUE, filters = f2) expect_false(nm %in% cl1$name) expect_true(nm %in% cl2$name) x$remove() }) test_that("prune", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) ans <- d$container$prune() expect_true(x$id() %in% ans$containers_deleted) }) test_that("diff", { ## TODO: need to get a better test in here - one that actually shows ## we can report this. For now it should be ok to show that the ## endpoint works, I guess. d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) xd <- x$diff() expect_is(xd, "data.frame") x$remove() }) test_that("export", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) ## TODO: here, and other export type ones - we need some optional ## 'filename' argument for dumping the export into. It might be ## worth a small wrapper around it (as a 'tar' class) so that we can ## interact more sensibly. We might also need to stream to disk - ## not memory via modifying the request. This would be done the ## same way as 'hijack' is done - but we'd pass through the required ## filename and go via curl::curl_fetch_disk rather than ## curl_fetch_memory. ## TODO: what does one _do_ with an exported container? tar <- x$export() expect_is(tar, "raw") path <- untar_bin(tar) on.exit(unlink(path, recursive = TRUE), add = TRUE) expect_true("hello" %in% dir(path)) x$remove() }) test_that("path_stat", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) on.exit(x$remove()) ## There's no spec here so not much more we can do dat <- x$path_stat("/") expect_is(dat, "list") expect_equal(dat$name, "/") e <- get_error(x$path_stat("foo")) expect_is(e, "docker_error") expect_equal(e$code, 404L) }) test_that("archive export", { ## TODO: another tar endpoint d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) on.exit(x$remove()) bin <- x$get_archive("hello", NULL) p <- untar_bin(bin) on.exit(unlink(p, recursive = TRUE), add = TRUE) expect_true(file.exists(file.path(p, "hello"))) path <- x$get_archive("hello", tempfile_test()) expect_true(file.exists(path)) expect_identical(readBin(path, raw(), file.size(path)), bin) expect_error(x$get_archive("hello", FALSE), "'dest' must be a scalar character (non-NA), or NULL", fixed = TRUE) }) test_that("archive import", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) on.exit(x$remove()) e <- get_error(x$path_stat("foo")) expect_equal(e$code, 404L) path <- tempfile_test() dir.create(path) dat <- rand_str(100) writeLines(dat, file.path(path, "foo")) bin <- tar_directory(path) ## Down here, we need to get the types correct for handling. x$put_archive(bin, "/") bin2 <- x$get_archive("foo", NULL) p <- untar_bin(bin2) on.exit(unlink(p, recursive = TRUE), add = TRUE) expect_true(file.exists(file.path(p, "foo"))) expect_identical(readLines(file.path(p, "foo")), dat) }) test_that("archive import (file)", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## x <- d$container$create("hello-world", name = nm) x <- d$container$create("bfirsh/reticulate-splines", name = nm) on.exit(x$remove(force = TRUE)) x$start() path <- tempfile_test() dir.create(path) dat <- rand_str(100) p <- file.path(path, "foo") writeLines(dat, p) ## Down here, we need to get the types correct for handling. x$put_archive(p, "/") tmp <- x$get_archive("foo", tempfile_test()) p2 <- untar_bin(read_binary(tmp)) on.exit(unlink(p2, recursive = TRUE), add = TRUE) expect_true(file.exists(file.path(p2, "foo"))) expect_identical(readLines(file.path(p2, "foo")), dat) }) test_that("kill", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("alpine", cmd = c("sleep", "10000"), name = nm) expect_identical(withVisible(x$start()), list(value = x, visible = FALSE)) expect_equal(x$status(), "running") expect_null(x$kill()) expect_equal(x$status(), "exited") x$remove() }) test_that("logs", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## TODO: this only prints out one per second - I'd like to up this ## to every 0.1s so that we can run this for less time. x <- d$container$create("bfirsh/reticulate-splines", name = nm) expect_identical(withVisible(x$start()), list(value = x, visible = FALSE)) logs <- x$logs() expect_is(logs, "docker_stream") expect_equal(logs[[1]], "Reticulating spline 1...\n") x$kill() x$remove() ## TODO: this can be tested separately from the docker bits too (and ## should be) expect_match(format(logs, style = "plain"), "^Reticulating spline \\d+...\n$") expect_match(format(logs, style = "prefix"), "^O> Reticulating spline \\d+...\n") expect_equal(all(crayon::has_style(format(logs, style = "colour"))), crayon::has_color()) expect_match(capture.output(print(logs, style = "plain")), "^Reticulating spline \\d+...$") expect_match(capture.output(print(logs, style = "prefix")), "^O> Reticulating spline \\d+...$") }) test_that("pause/unpause", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## TODO: this only prints out one per second - I'd like to up this ## to every 0.1s so that we can run this for less time. x <- d$container$create("bfirsh/reticulate-splines", name = nm) x$start() expect_null(x$pause()) expect_equal(x$status(), "paused") e <- get_error(x$pause()) expect_is(e, "docker_error") ## This is not actually in the spec, but it returns 409 (Conflict) ## on pause of paused container - unpausing an unpaused container ## returns 500 though so this does not seem reliable. ## expect_equal(e$code, 409) expect_null(x$unpause()) expect_equal(x$status(), "running") e <- get_error(x$unpause()) expect_is(e, "docker_error") x$kill() x$remove() }) test_that("rename", { d <- test_docker_client() nm1 <- rand_str(10, "stevedore_") nm2 <- rand_str(10, "stevedore_") ## TODO: this only prints out one per second - I'd like to up this ## to every 0.1s so that we can run this for less time. x <- d$container$create("bfirsh/reticulate-splines", name = nm1) expect_null(x$rename(nm2)) expect_equal(x$reload()$name(), nm2) x$remove() }) test_that("restart", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) x$start() Sys.sleep(0.5) logs1 <- x$logs() expect_null(x$restart(0)) Sys.sleep(0.5) logs2 <- x$logs() expect_equal(sum(logs2 == "Reticulating spline 1...\n"), 2) x$kill() x$remove() }) test_that("stats", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) s <- x$stats() ## There is no spec here so I can't really test much here except ## that this does exist. Note that this is quite slow if using a ## running container! expect_is(s, "list") x$remove() }) test_that("stop", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) x$start() expect_null(x$stop(0)) expect_equal(x$status(), "exited") x$remove() }) test_that("top", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) x$start() t <- x$top() expect_is(t, "data.frame") expect_true("PID" %in% names(t)) x$kill() x$remove() }) test_that("top works with custom data.frame handler", { d <- test_docker_client(data_frame = dummy_data_frame_wrapper) nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) x$start() t <- x$top() x$kill() x$remove() expect_is(t, "data.frame") expect_is(t, "extra") }) test_that("update", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("bfirsh/reticulate-splines", name = nm) on.exit(x$remove()) info <- x$inspect(FALSE) n <- as.integer(10 * 1001 * 1001) y <- x$update(memory = n, memory_swap = -1L) expect_identical(x, y) info2 <- y$inspect(TRUE) expect_identical(info2$host_config$memory, n) expect_identical(info2$host_config$memory_swap, -1L) }) test_that("wait", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) x$start() expect_equal(x$wait(), list(exit_code = 0L)) expect_equal(x$status(), "exited") x$remove() }) test_that("prune", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) ans <- d$container$prune() expect_true(x$id() %in% ans$containers_deleted) e <- get_error(x$status()) expect_is(e, "docker_error") expect_equal(e$code, 404L) }) test_that("image", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("hello-world", name = nm) img <- x$image() cmp <- d$image$get("hello-world") expect_equal(cmp$inspect(), img$inspect()) x$remove() }) test_that("exec_create", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## this sets up a container that will run forever x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm) x$start() ans <- x$exec_create("ls") expect_is(ans, "docker_exec") expect_is(ans, "stevedore_object") info <- ans$inspect() expect_false(info$running) expect_true(info$open_stdout) expect_true(info$open_stderr) txt <- capture.output(res <- ans$start(detach = FALSE)) expect_is(res, "docker_stream") cmp <- unlist(strsplit(format(res, style = "prefix"), "\n")) expect_equal(txt, cmp) x$kill() x$remove() }) test_that("exec: in one", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## this sets up a container that will run forever x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm) ## This is almost there but detach needs setting properly to become ## FALSE by default or something. x$start() txt <- capture.output(res <- x$exec("ls")) expect_is(res$output, "docker_stream") cmp <- unlist(strsplit(format(res$output, style = "prefix"), "\n")) expect_equal(txt, cmp) expect_equal(res$exit_code, 0L) expect_match(res$id, "^[[:xdigit:]]+$") x$kill() x$remove() }) test_that("exec, twice", { skip("wip") d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## this sets up a container that will run forever x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm) x$start() ans <- x$exec_create("ls") ans$start() ## This does not throw but it should! The issue here is that docker ## is not actuall creating error codes here. So some more care ## might be needed. Probably best not to return prepared exec ## instances by default! expect_error(ans$start()) }) test_that("run", { d <- test_docker_client() txt <- capture.output(ans <- d$container$run("hello-world")) on.exit(ans$container$remove()) expect_is(ans, "docker_run_output") expect_equal(names(ans), c("container", "logs")) expect_is(ans$container, "docker_container") expect_is(ans$logs, "docker_stream") expect_equal(txt, format(ans$logs, strip_newline = TRUE, style = "prefix")) txt <- capture.output(print(ans, style = "prefix")) expect_match(txt, "", fixed = TRUE, all = FALSE) expect_match(txt, " $container", fixed = TRUE, all = FALSE) expect_match(txt, " $logs", fixed = TRUE, all = FALSE) }) test_that("run: detach", { d <- test_docker_client() ans <- d$container$run("richfitz/iterate", c("10", "0.1"), detach = TRUE) expect_is(ans, "docker_container") expect_equal(ans$wait(), list("exit_code" = 0L)) ans$remove() }) test_that("run: no such container", { d <- test_docker_client() msg <- capture_messages( e <- get_error(d$container$run("richfitz/nosuchcontainer"))) expect_match( msg, "Unable to find image 'richfitz/nosuchcontainer:latest' locally", fixed = TRUE, all = FALSE) # allow additional here expect_is(e, "docker_error") ## TODO: the endpoint registering as failed here is image_create, ## not image_pull which is the one that does fail. This is because ## docker_get_image uses tryCatch and *rethrows* the error but what ## we should do is withCallingHandlers and then avoid throwing at ## all if we can recover. if (has_internet()) { expect_equal(e$code, 404L) } else { expect_equal(e$code, 500L) } expect_equal(e$endpoint, "image_create") }) test_that("run with get/pull error handling", { d <- test_docker_client() skip("currently broken on ci") err <- get_error(d$container$run("foo bar")) expect_is(err, "docker_error") expect_equal(err$code, 400L) # bad request }) test_that("run: remove", { d <- test_docker_client() ans <- d$container$run("hello-world", rm = TRUE, stream = FALSE) expect_equal(names(ans), c("container", "logs")) expect_is(ans$container, "docker_container") expect_is(ans$logs, "docker_stream") e <- get_error(d$container$get(ans$container$id())) expect_true(is_docker_error_not_found(e)) }) test_that("run: error", { d <- test_docker_client() e <- get_error( d$container$run("richfitz/error", "4", rm = TRUE, stream = FALSE)) expect_is(e, "container_error") expect_equal( e$message, "Command '4' in image 'richfitz/error:latest' returned non-zero exit status 4", fixed = TRUE) expect_is(e$container, "docker_container") expect_equal(e$exit_status, 4L) expect_equal(e$cmd, "4") expect_equal(e$image$id(), d$image$get("richfitz/error")$id()) expect_is(e$out, "docker_stream") expect_equal(e$out, docker_stream("throwing error 4\n", 1L)) }) test_that("run: error to stderr", { d <- test_docker_client() e <- get_error( d$container$run("richfitz/error", "14", rm = TRUE, stream = FALSE)) expect_is(e, "container_error") expect_equal( e$message, "Command '14' in image 'richfitz/error:latest' returned non-zero exit status 14\nthrowing error 14 to stderr\n", fixed = TRUE) }) test_that("run with image", { d <- test_docker_client() img <- d$image$get("hello-world") ans <- d$container$run(img, rm = TRUE, stream = FALSE) expect_is(ans$logs, "docker_stream") }) test_that("scalar cmd", { d <- test_docker_client() c1 <- d$container$create("alpine:3.1", I("echo hello world")) dat <- c1$inspect(FALSE) expect_identical(dat$path, "echo") expect_identical(dat$args, c("hello", "world")) c1$start() c1$wait() log <- c1$logs() expect_equal(format(log, style = "plain", filter = "stdout"), "hello world\n") c1$remove() }) test_that("scalar exec", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") ## this sets up a container that will run forever x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm) x$start() ans <- x$exec_create(I("echo hello world")) dat <- ans$inspect(FALSE) expect_equal(dat$process_config$entrypoint, "echo") expect_equal(dat$process_config$arguments, c("hello", "world")) capture.output(res <- ans$start(detach = FALSE)) expect_equal(format(res, style = "plain", filter = "stdout"), "hello world\n") x$kill() x$remove() }) test_that("stream logs", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") p <- tempfile_test() x <- d$container$create("richfitz/iterate", cmd = c("10", "0.1"), name = nm) x$start() res <- x$logs(follow = TRUE, stream = p) expect_is(res, "docker_stream") cmp <- readLines(p) expect_equal(format(res, style = "prefix"), paste0(cmp, "\n")) x$remove() }) test_that("fetch, but don't print, logs", { ## Getting some stray beginning of lines using follow on a stopped ## container - I *bet* that there's a cat d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("richfitz/iterate", cmd = c("10", "0.1"), name = nm) x$start() x$wait() p <- tempfile_test() expect_silent(v1 <- x$logs(stream = NULL, follow = TRUE)) expect_silent(v2 <- x$logs(stream = p, follow = TRUE)) txt <- capture.output(v3 <- x$logs(stream = stdout(), follow = TRUE)) expect_match(txt, "O> Done!", all = FALSE, fixed = TRUE) expect_is(v1, "docker_stream") expect_is(v2, "docker_stream") expect_is(v3, "docker_stream") expect_equal(format(v1, style = "prefix"), paste0(txt, "\n")) expect_equal(format(v2, style = "prefix"), paste0(txt, "\n")) expect_equal(format(v3, style = "prefix"), paste0(txt, "\n")) x$remove() }) test_that("auto-remove: create", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create( "alpine:3.1", cmd = c("echo", "hello world"), name = nm, host_config = list(AutoRemove = jsonlite::unbox(TRUE))) expect_true(x$inspect(FALSE)$host_config$auto_remove) x$start() x$wait() e <- repeat_until_error(x$inspect) expect_equal(e$code, 404L) }) test_that("auto-remove: run", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$run( "alpine:3.1", cmd = c("echo", "hello world"), name = nm, rm = TRUE, detach = TRUE) expect_true(x$inspect(FALSE)$host_config$auto_remove) x$wait() e <- repeat_until_error(x$inspect) expect_equal(e$code, 404L) }) ## TODO: there is not a test in here (or anywhere really) that this ## shows that this actually streams logs from the beginning (rather ## than just going all at the end). test_that("stream", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") p <- tempfile_test() x <- d$container$run("richfitz/iterate", cmd = c("10", "0.1"), name = nm, detach = FALSE, stream = p, rm = TRUE) expect_equal(paste0(readLines(p), "\n"), format(x$logs, style = "prefix")) }) test_that("volume map", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") p <- tempfile_test() dir.create(p) on.exit(unlink(p, recursive = TRUE), add = TRUE) v <- sprintf("%s:%s", getwd(), "/host") x <- d$container$create("alpine:latest", cmd = c("ls", "/host"), name = nm, volumes = v) x$start() x$wait() expect_equal(sort(trimws(format(x$logs(), style = "plain"))), sort(dir())) x$remove() }) test_that("volume map: docker volume", { skip_on_travis() # FIXME d <- test_docker_client() volume <- d$volume$create("myvolume") nm <- rand_str(10, "stevedore_") v <- sprintf("%s:%s", volume$name(), "/host") x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm, volumes = v) on.exit({ x$remove(force = TRUE) volume$remove() }) x$start() x$exec(c("touch", "/host/foo"), stderr = FALSE, stdout = FALSE) y <- d$container$create("alpine", cmd = c("ls", "/host"), name = rand_str(10, "stevedore_"), volumes = v) y$start() y$wait() expect_equal(trimws(format(y$logs(), style = "plain")), "foo") y$remove() }) test_that("volume map: readonly", { skip_on_travis() # FIXME d <- test_docker_client() volume <- d$volume$create("avolume") nm <- rand_str(10, "stevedore_") dest <- "/host" x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm, volumes = volume$map(dest, TRUE)) on.exit({ x$remove(force = TRUE) volume$remove() }) x$start() x$exec(c("touch", "/host/foo"), stdout = FALSE, stderr = FALSE) ans <- x$exec(c("ls", "/host"), stream = FALSE) expect_equal(ans$output, character(0)) }) test_that("port map", { ## Roughly equivalent to: ## docker run --rm -p 10080:80 nginx port <- "10080" # port to use - hopefully clear ports <- sprintf("%s:80", port) d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("nginx", name = nm, ports = ports) x$start() on.exit(x$remove(force = TRUE)) url <- sprintf("http://127.0.0.1:%s/", port) ## This test is a little flakey to start - so retry up to 5 times ## (with a 1/2s delay between each) for (i in 1:5) { dat <- tryCatch(curl::curl_fetch_memory(url), error = function(e) NULL) if (!is.null(dat)) { break } Sys.sleep(0.5) } expect_equal(dat$status_code, 200L) expect_true(grepl("nginx", rawToChar(dat$content))) }) test_that("port map - random free port", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("nginx", name = nm, ports = 80L) x$start() on.exit(x$remove(force = TRUE)) ports <- x$ports() expect_is(ports, "data.frame") expect_equal(names(ports), c("container_port", "protocol", "host_ip", "host_port")) expect_true(all(vlapply(ports, is.character))) expect_equal(ports$protocol[[1]], "tcp") expect_equal(ports$host_ip[[1]], "0.0.0.0") expect_equal(ports$container_port[[1]], "80") }) test_that("port map - expose all ports", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("nginx", name = nm, ports = TRUE) on.exit(x$remove(force = TRUE)) x$start() ports <- x$ports() expect_identical(ports$container_port[[1]], "80") }) test_that("query ports of container with none", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("richfitz/iterate", cmd = c("100", "100"), name = nm) ports <- x$ports() expect_equal(ports, data_frame(container_port = character(0), protocol = character(0), host_ip = character(0), host_port = character(0))) x$remove() }) test_that("port map - data.frame map", { d <- test_docker_client(data_frame = dummy_data_frame_wrapper) nm <- rand_str(10, "stevedore_") x <- d$container$create("nginx", name = nm, ports = TRUE) on.exit(x$remove(force = TRUE)) x$start() ports <- x$ports() expect_identical(ports$container_port[[1]], "80") expect_is(ports, "extra") expect_is(ports, "data.frame") }) test_that("network: host", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- d$container$create("nginx", name = nm, ports = "80") x$start() on.exit(x$remove(force = TRUE), add = TRUE) port <- x$ports()$host_port ## docker run --rm --network=host richfitz/curl -s http://localhost:32776 ## docker run --rm richfitz/curl -s http://localhost:32776 image <- "richfitz/curl" args <- c("-s", sprintf("http://localhost:%s", port)) ## This will fail because we don't have host networking y <- d$container$create(image, args) on.exit(y$remove(force = TRUE), add = TRUE) y$start() code <- y$wait() expect_false(code$exit_code == 0) expect_equal(format(y$logs(), style = "plain"), character(0)) ## Then with host networking z <- d$container$create(image, args, network = "host") on.exit(z$remove(force = TRUE), add = TRUE) z$start() code <- z$wait() expect_true(code$exit_code == 0) expect_match(format(z$logs(), style = "plain"), "nginx", all = FALSE) }) test_that("network: custom", { ## Here's the scenario: ## ## docker network create testing ## docker run --network=testing --rm -d --name server nginx ## docker run --network=testing --rm richfitz/curl -s http://server ## docker stop server ## docker network rm testing server <- rand_str(10, "stevedore_") client <- rand_str(10, "stevedore_") network <- rand_str(3, "stevedore_") url <- paste0("http://", server) d <- test_docker_client() nw <- d$network$create(network) on.exit({ if (exists("x")) { x$remove(force = TRUE) } if (exists("y")) { y$remove(force = TRUE) } nw$remove() }) x <- d$container$create("nginx", name = server, network = nw) x$start() y <- d$container$create("richfitz/curl", c("-s", server), network = network, name = client) y$start() code <- y$wait() expect_true(code$exit_code == 0) expect_match(format(y$logs(), style = "plain"), "nginx", all = FALSE) }) test_that("logs with tty", { ## > When the TTY setting is enabled in POST /containers/create, the ## > stream is not multiplexed. The data exchanged over the hijacked ## > connection is simply the raw data from the process PTY and ## > client's stdin. d <- test_docker_client() nm1 <- rand_str(10, "stevedore_") nm2 <- rand_str(10, "stevedore_") p <- tempfile_test() x <- d$container$create("richfitz/iterate", cmd = c("10", "0.1"), name = nm1, tty = TRUE) y <- d$container$create("richfitz/iterate", cmd = c("10", "0.1"), name = nm2, tty = FALSE) x$start() y$start() x$wait() y$wait() res1 <- x$logs() res2 <- y$logs() expect_is(res1, "character") expect_equal(res1[[length(res1)]], "Done!") expect_equal(res1, trimws(format(res2, style = "plain"))) x$remove() y$remove() }) test_that("stream logs with tty", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") p <- tempfile_test() x <- d$container$create("richfitz/iterate", cmd = c("10", "0.1"), name = nm, tty = TRUE) x$start() res <- x$logs(follow = TRUE, stream = p) expect_is(res, "character") cmp <- readLines(p) expect_equal(res, cmp) x$remove() }) test_that("run, passing through host_config", { d <- test_docker_client() nm <- rand_str(10, "stevedore_") p <- tempfile_test() dir.create(p) on.exit(unlink(p, recursive = TRUE), add = TRUE) v <- sprintf("%s:%s", getwd(), "/host") res <- d$container$run("alpine:latest", cmd = c("ls", "/host"), stream = FALSE, name = nm, volumes = v, rm = TRUE) expect_equal(sort(trimws(format(res$logs, style = "plain"))), sort(dir())) e <- get_error(res$container$status()) expect_true(is_docker_error_not_found(e)) }) test_that("commit", { d <- test_docker_client() x <- d$container$run("alpine:3.1", c("tar", "-zcvf", "/etc.tar.gz", "/etc"), stream = FALSE) on.exit(x$container$remove()) expect_equal(x$container$diff(), data_frame(path = "/etc.tar.gz", kind = 1L)) img <- x$container$commit("alpine-with-etc", "latest") h <- img$history() expect_match(h[1, "created_by"], "^tar ") files <- d$container$run(img, c("ls", "/"), rm = TRUE, stream = FALSE)$logs expect_true("etc.tar.gz" %in% trimws(files)) img$remove() }) test_that("versioned responses", { skip("work in progress") ## TODO: this is broken for 25..28 inclusive. The fix is to get the ## newer port definition here, but I don't know if that is because ## the spec (and responses) are wrong or if it's because new clients ## do not return the correct information. This is a bit of a faff ## really. d1 <- test_docker_client(api_version = test_docker_versions[[1]]) nm <- rand_str(10, "stevedore_") ## options(error = recover) x <- d1$container$create("nginx", ports = TRUE, name = nm) ## Error is with data: named empty list (json: {}) ## From 'Ports' d2 <- test_docker_client(api_version = tail(test_docker_versions, 1)) x <- d2$container$create("nginx", ports = TRUE) ## on.exit(x$remove(force = TRUE)) x$start() x$ports() y <- d1$container$get(x$id()) x$remove(force = TRUE) }) test_that("environment variables on create", { d <- test_docker_client() res <- d$container$run("alpine", "env", detach = FALSE, stream = NULL, env = c("X_FOO" = "1"), rm = TRUE) txt <- format(res$logs, style = "plain", strip_newline = TRUE) expect_true("X_FOO=1" %in% txt) }) test_that("set user", { skip_on_windows() d <- test_docker_client() uid <- user(group = TRUE) res1 <- d$container$run("alpine:3.1", "id", detach = FALSE, stream = NULL, rm = TRUE) res2 <- d$container$run("alpine:3.1", "id", user = uid, detach = FALSE, stream = NULL, rm = TRUE) parse_id <- function(x) { x <- strsplit(x, "\\s+")[[1]] xx <- strsplit(x, "=", fixed = TRUE) label <- vcapply(xx, "[[", 1L) value <- vcapply(xx, "[[", 2L) paste(sub("[^0-9].*", "", value[label == "uid"]), sub("[^0-9].*", "", value[label == "gid"]), sep = ":") } expect_equal(parse_id(res1$logs), "0:0") expect_equal(parse_id(res2$logs), uid) }) test_that("get (offline)", { cl <- null_docker_client() x <- cl$container$get(dummy_id()) expect_is(x, "docker_container") expect_equal(x$id(), dummy_id()) }) test_that("process ports", { opts <- list(data_frame = identity) expect_equal(docker_container_ports(NULL, opts), data_frame(container_port = character(0), protocol = character(0), host_ip = character(0), host_port = character(0))) d <- list("80/tcp" = data_frame(host_ip = "0.0.0.0", host_port = "32789")) attrs <- list(network_settings = list(ports = d)) expect_equal(docker_container_ports(attrs, opts), data_frame(container_port = "80", protocol = "tcp", host_ip = "0.0.0.0", host_port = "32789")) cl <- null_docker_client() x <- cl$container$get(dummy_id()) expect_equal(x$ports(), data_frame(container_port = character(0), protocol = character(0), host_ip = character(0), host_port = character(0))) }) test_that("cp_in single file", { cl <- test_docker_client() tmp1 <- tempfile() tmp2 <- tempfile() writeLines("hello world", tmp1) writeLines("goodbye world", tmp2) nm <- rand_str(10, "stevedore_") x <- cl$container$run("richfitz/iterate", cmd = c("100", "100"), name = nm, rm = TRUE, detach = TRUE) on.exit({ x$kill() unlink(c(tmp1, tmp2)) }) remote_read <- function(path, x) { as.character(x$exec(c("cat", path), stream = FALSE)$output) } ## file => nonexistant (and create) x$cp_in(tmp1, "tmp/myfile") expect_equal(remote_read("tmp/myfile", x), "hello world\n") ## file => file (and overwrite) x$cp_in(tmp2, "tmp/myfile") expect_equal(remote_read("tmp/myfile", x), "goodbye world\n") ## file => directory x$cp_in(tmp1, "tmp") expect_equal(remote_read(file.path("tmp", basename(tmp1)), x), "hello world\n") ## file => missing dir expect_error(x$cp_in(tmp1, "missingdir/"), "'dest' is a directory but does not exist on container") }) test_that("cp in a directory", { cl <- test_docker_client() tmp1 <- tempfile() tmp2 <- tempfile() dir.create(tmp1) dir.create(tmp2) writeLines("hello world", file.path(tmp1, "a")) writeLines("goodbye world", file.path(tmp2, "a")) nm <- rand_str(10, "stevedore_") x <- cl$container$run("richfitz/iterate", cmd = c("100", "100"), name = nm, rm = TRUE, detach = TRUE) on.exit({ x$kill() unlink(c(tmp1, tmp2), recursive = TRUE) }) remote_read <- function(path, x) { as.character(x$exec(c("cat", path), stream = FALSE)$output) } ## directory => nonexistant (and create) x$cp_in(tmp1, "tmp/mydir") expect_equal(remote_read("tmp/mydir/a", x), "hello world\n") ## file => existing directory x$cp_in(tmp2, "tmp") filename <- sprintf("tmp/%s/a", basename(tmp2)) expect_equal(remote_read(filename, x), "goodbye world\n") ## directory => file expect_error(x$cp_in(tmp2, "/usr/local/bin/iterate"), "as dest '/usr/local/bin/iterate' is a file") }) test_that("cp_out single file", { cl <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- cl$container$run("richfitz/iterate", cmd = c("100", "100"), name = nm, rm = TRUE, detach = TRUE) on.exit({ x$kill() unlink(c(tmp1, tmp2)) }) txt <- readLines("images/iterate/iterate") ## file => dir tmp1 <- tempfile() dir.create(tmp1) x$cp_out("/usr/local/bin/iterate", tmp1) expect_equal(readLines(file.path(tmp1, "iterate")), txt) ## file => nonexistant tmp2 <- tempfile() x$cp_out("/usr/local/bin/iterate", tmp2) expect_equal(readLines(tmp2), txt) ## file => existing file tmp3 <- tempfile() writeLines("hello", tmp3) x$cp_out("/usr/local/bin/iterate", tmp3) expect_equal(readLines(tmp3), txt) ## file => missing directory tmp4 <- file.path(tempfile(), "subdir") expect_error(x$cp_out("/usr/local/bin/iterate", tmp4), "Destination directory does not exist") }) test_that("cp_out directory", { cl <- test_docker_client() nm <- rand_str(10, "stevedore_") x <- cl$container$run("richfitz/iterate", cmd = c("100", "100"), name = nm, rm = TRUE, detach = TRUE) on.exit({ x$kill() unlink(c(tmp1, tmp2)) }) txt <- readLines("images/iterate/iterate") ## dir => dir tmp1 <- tempfile() dir.create(tmp1) x$cp_out("/usr/local/bin", tmp1) expect_equal(readLines(file.path(tmp1, "bin", "iterate")), txt) ## file => nonexistant tmp2 <- tempfile() x$cp_out("/usr/local/bin", tmp2) expect_equal(readLines(file.path(tmp2, "iterate")), txt) ## file => existing file tmp3 <- tempfile() writeLines("hello", tmp3) expect_error(x$cp_out("/usr/local/bin", tmp3), "Can't overwrite file '.+' with directory '.+'") })