summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml24
-rw-r--r--cmd/podman/cleanup.go1
-rw-r--r--cmd/podman/cliconfig/config.go7
-rw-r--r--cmd/podman/run.go10
-rw-r--r--completions/bash/podman1
-rwxr-xr-xcontrib/cirrus/logformatter437
-rw-r--r--docs/source/markdown/podman-container-cleanup.1.md8
-rw-r--r--docs/source/markdown/podman-run.1.md5
-rw-r--r--pkg/adapter/containers.go19
-rw-r--r--pkg/spec/createconfig.go5
-rw-r--r--test/apiv2/20-containers.at2
-rw-r--r--test/apiv2/22-stop.at24
-rw-r--r--test/e2e/libpod_suite_test.go2
-rw-r--r--test/system/030-run.bats17
14 files changed, 549 insertions, 13 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index 151153b14..39b2bdc1a 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -27,6 +27,9 @@ env:
# (can't do inline awk script, Cirrus-CI or YAML mangles quoting)
TIMESTAMP: "awk --file ${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/timestamp.awk"
+ # HTMLify ginkgo and bats logs
+ LOGFORMAT: "${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/logformatter"
+
####
#### Cache-image names to test with (double-quotes around names are critical)
###
@@ -396,9 +399,9 @@ testing_task:
networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh'
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
unit_test_script: '$SCRIPT_BASE/unit_test.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
- system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}'
- apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
+ system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} system_test'
+ apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} apiv2_test'
build_release_script: '$SCRIPT_BASE/build_release.sh |& ${TIMESTAMP}'
# For PRs this confirms uploading releases after merge, is functional.
upload_release_archive_script: '$SCRIPT_BASE/upload_release_archive.sh |& ${TIMESTAMP}'
@@ -419,6 +422,9 @@ testing_task:
journal_script: '$SCRIPT_BASE/logcollector.sh journal'
varlink_script: '$SCRIPT_BASE/logcollector.sh varlink'
podman_system_info_script: '$SCRIPT_BASE/logcollector.sh podman'
+ html_artifacts:
+ path: "*.log.html"
+ type: "text/html"
# This task executes tests under unique environments/conditions
@@ -446,9 +452,9 @@ special_testing_rootless_task:
networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh'
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
- system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP}'
- apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
+ system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} system_test'
+ apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} apiv2_test'
on_failure:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
@@ -490,7 +496,7 @@ special_testing_in_podman_task:
networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh'
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
on_failure:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
@@ -552,7 +558,7 @@ special_testing_bindings_task:
timeout_in: 40m
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
on_failure:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
@@ -578,7 +584,7 @@ special_testing_endpoint_task:
timeout_in: 20m
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
on_failure:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go
index a8bc0c116..80a19b000 100644
--- a/cmd/podman/cleanup.go
+++ b/cmd/podman/cleanup.go
@@ -44,6 +44,7 @@ func init() {
flags.BoolVarP(&cleanupCommand.All, "all", "a", false, "Cleans up all containers")
flags.BoolVarP(&cleanupCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&cleanupCommand.Remove, "rm", false, "After cleanup, remove the container entirely")
+ flags.BoolVar(&cleanupCommand.RemoveImage, "rmi", false, "After cleanup, remove the image entirely")
markFlagHiddenForRemoteClient("latest", flags)
}
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index ccc30c603..79917946a 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -658,9 +658,10 @@ type VolumeRmValues struct {
type CleanupValues struct {
PodmanCommand
- All bool
- Latest bool
- Remove bool
+ All bool
+ Latest bool
+ Remove bool
+ RemoveImage bool
}
type SystemPruneValues struct {
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index 219f057c3..27247c5b5 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/pkg/adapter"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -38,6 +39,7 @@ func init() {
flags.SetInterspersed(false)
flags.SetNormalizeFunc(aliasFlags)
flags.Bool("sig-proxy", true, "Proxy received signals to the process")
+ flags.Bool("rmi", false, "Remove container image unless used by other containers")
flags.AddFlagSet(getNetFlags())
getCreateFlags(&runCommand.PodmanCommand)
markFlagHiddenForRemoteClient("authfile", flags)
@@ -64,5 +66,13 @@ func runCmd(c *cliconfig.RunValues) error {
defer runtime.DeferredShutdown(false)
exitCode, err = runtime.Run(getContext(), c, exitCode)
+ if c.Bool("rmi") {
+ imageName := c.InputArgs[0]
+ if newImage, newImageErr := runtime.NewImageFromLocal(imageName); newImageErr != nil {
+ logrus.Errorf("%s", errors.Wrapf(newImageErr, "failed creating image object"))
+ } else if _, errImage := runtime.RemoveImage(getContext(), newImage, false); errImage != nil {
+ logrus.Errorf("%s", errors.Wrapf(errImage, "failed removing image"))
+ }
+ }
return err
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 13be64e06..895659fe5 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1967,6 +1967,7 @@ _podman_container_run() {
boolean_options="$boolean_options
--detach -d
--rm
+ --rmi
--sig-proxy=false
"
__podman_complete_detach_keys && return
diff --git a/contrib/cirrus/logformatter b/contrib/cirrus/logformatter
new file mode 100755
index 000000000..4cc4480f5
--- /dev/null
+++ b/contrib/cirrus/logformatter
@@ -0,0 +1,437 @@
+#!/usr/bin/perl
+#
+# logformatter - highlight a Cirrus test log (ginkgo or bats)
+#
+# Adapted from https://raw.githubusercontent.com/edsantiago/greasemonkey/podman-ginkgo-highlight
+#
+package LibPod::CI::LogFormatter;
+
+use v5.14;
+use utf8;
+
+# Grumble. CI system doesn't have 'open'
+binmode STDIN, ':utf8';
+binmode STDOUT, ':utf8';
+
+use strict;
+use warnings;
+
+(our $ME = $0) =~ s|.*/||;
+
+our $VERSION = '0.1';
+
+# For debugging, show data structures using DumpTree($var)
+#use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0;
+
+###############################################################################
+# BEGIN user-customizable section
+
+# Stylesheet for highlighting or de-highlighting parts of lines
+our $CSS = <<'END_CSS';
+/* wrap long lines - don't require user to scroll right */
+pre { line-break: normal; overflow-wrap: normal; white-space: pre-wrap; }
+
+.boring { color: #999; }
+.timestamp { color: #999; }
+.log-debug { color: #999; }
+.log-info { color: #333; }
+.log-warn { color: #f60; }
+.log-error { color: #900; font-weight: bold; }
+.subtest { background: #eee; }
+.subsubtest { color: #F39; font-weight: bold; }
+.string { color: #00c; }
+.command { font-weight: bold; color: #000; }
+.changed { color: #000; font-weight: bold; }
+
+/* links to source files: not as prominent as links to errors */
+a.codelink:link { color: #000; }
+a.codelink:visited { color: #666; }
+a.codelink:hover { background: #000; color: #999; }
+
+/* The timing tests at bottom: remove underline, it's too cluttery. */
+a.timing { text-decoration: none; }
+
+/* BATS styles */
+.bats-ok { color: #3f3; }
+.bats-notok { color: #F00; font-weight: bold; }
+.bats-skip { color: #F90; }
+.bats-log { color: #900; }
+.bats-log-esm { color: #b00; font-weight: bold; }
+
+/* error titles: display next to timestamp, not on separate line */
+h2 { display: inline; }
+END_CSS
+
+# END user-customizable section
+###############################################################################
+
+###############################################################################
+# BEGIN boilerplate args checking, usage messages
+
+sub usage {
+ print <<"END_USAGE";
+Usage: $ME [OPTIONS] TEST_NAME
+
+$ME is a filter; it HTMLifies an input stream (presumably
+Ginkgo or BATS log results), writing HTML results to an output file
+but passing stdin unmodified to stdout. It is intended to run in
+the Cirrus CI environment.
+
+Parameters:
+
+ TEST_NAME descriptive name; output file will be TEST_NAME.log.html
+
+OPTIONS:
+
+ --help display this message
+ --man display program man page
+ --version display program name and version
+END_USAGE
+
+ exit;
+}
+
+# Command-line options. Note that this operates directly on @ARGV !
+our $debug = 0;
+our $force = 0;
+our $verbose = 0;
+our $NOT = ''; # print "blahing the blah$NOT\n" if $debug
+sub handle_opts {
+ use Getopt::Long;
+ GetOptions(
+ 'debug!' => \$debug,
+ 'dry-run|n!' => sub { $NOT = ' [NOT]' },
+ 'force' => \$force,
+ 'verbose|v' => \$verbose,
+
+ help => \&usage,
+ version => sub { print "$ME version $VERSION\n"; exit 0 },
+ ) or die "Try `$ME --help' for help\n";
+}
+
+# END boilerplate args checking, usage messages
+###############################################################################
+
+############################## CODE BEGINS HERE ###############################
+
+# The term is "modulino".
+__PACKAGE__->main() unless caller();
+
+# Main code.
+sub main {
+ # Note that we operate directly on @ARGV, not on function parameters.
+ # This is deliberate: it's because Getopt::Long only operates on @ARGV
+ # and there's no clean way to make it use @_.
+ handle_opts(); # will set package globals
+
+ # In case someone is tempted to run us on the command line
+ die "$ME: this is a filter, not an interactive script\n" if -t *STDIN;
+
+ # Fetch command-line arguments. Barf if too many.
+ my $test_name = shift(@ARGV)
+ or die "$ME: missing TEST_NAME argument; try $ME --help\n";
+ warn "$ME: Too many arguments; ignoring extras. try $ME --help\n" if @ARGV;
+
+ format_log($test_name);
+}
+
+
+sub format_log {
+ my $test_name = shift; # in: e.g. 'integration_test'
+
+ my $outfile = "$test_name.log.html";
+ my $out_tmp = "$outfile.tmp.$$";
+ open my $out_fh, '>:utf8', $out_tmp
+ or warn "$ME: Cannot create $out_tmp: $!\n";
+
+ # Boilerplate: HTML headers for output file
+ print { $out_fh } <<"END_HTML" if $out_fh;
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>$test_name</title>
+<style type="text/css">
+$CSS
+</style>
+
+<!-- on page load, go to bottom: that's where the error summary is -->
+<script language="javascript">
+function scrollToBottom() {
+ if (window.scrollY < 10) {
+ window.scrollTo(0, document.body.scrollHeight);
+ }
+}
+window.addEventListener("load", scrollToBottom, false);
+</script>
+</head>
+<body>
+<pre>
+END_HTML
+
+ # State variables
+ my $previous_timestamp = ''; # timestamp of previous line
+ my $cirrus_task; # Cirrus task number, used for linking
+ my $git_commit; # git SHA, used for linking to source files
+ my $in_failure; # binary flag: are we in an error dump?
+ my $in_timing; # binary flag: are we in the timing section?
+ my $after_divider = 0; # Count of lines after seeing '-----'
+ my $current_output; # for removing duplication
+ my $looks_like_bats; # binary flag: for detecting BATS results
+
+ # Main loop: read input, one line at a time, and write out reformatted
+ LINE:
+ while (my $line = <STDIN>) {
+ print $line; # Immediately dump back to stdout
+
+ # Remain robust in face of errors: always write stdout even if no HTML
+ next LINE if ! $out_fh;
+
+ chomp $line;
+ $line =~ s/\0//g; # Some log files have NULs????
+ $line = escapeHTML($line);
+
+ # Temporarily strip off leading timestamp
+ $line =~ s/^(\[\+\d+s\]\s)//;
+ my $timestamp = $1 || '';
+ if ($previous_timestamp && $timestamp eq $previous_timestamp) {
+ $timestamp = ' ' x length($timestamp);
+ }
+ elsif ($timestamp) {
+ $previous_timestamp = $timestamp;
+ }
+
+ # Try to identify the git commit we're working with...
+ if ($line =~ m!libpod/define.gitCommit=([0-9a-f]+)!) {
+ $git_commit = $1;
+ }
+ # ...so we can link to specific lines in source files
+ if ($git_commit) {
+ # 1 12 3 34 4 5 526 6
+ $line =~ s{^(.*)(\/(containers\/libpod)(\/\S+):(\d+))(.*)$}
+ {$1<a class="codelink" href='https://github.com/$3/blob/$git_commit$4#L$5'>$2</a>$6};
+ }
+
+ # Try to identify the cirrus task
+ if ($line =~ /cirrus-task-(\d+)/) {
+ $cirrus_task = $1;
+ }
+
+ # BATS handling
+ if ($line =~ /^1\.\.\d+$/) {
+ $looks_like_bats = 1;
+ }
+ if ($looks_like_bats) {
+ my $css;
+
+ if ($line =~ /^ok\s.*\s# skip/) { $css = 'skip' }
+ elsif ($line =~ /^ok\s/) { $css = 'ok' }
+ elsif ($line =~ /^not\s+ok\s/) { $css = 'notok' }
+ elsif ($line =~ /^#\s#\|\s/) { $css = 'log-esm' }
+ elsif ($line =~ /^#\s/) { $css = 'log' }
+
+ if ($css) {
+ $line = "<span class='bats-$css'>$line</span>";
+ }
+
+ print { $out_fh } "<span class=\"timestamp\">$timestamp</span>"
+ if $timestamp;
+ print { $out_fh } $line, "\n";
+ next LINE;
+ }
+
+ # Timing section at the bottom of the page
+ if ($line =~ / timing results\s*$/) {
+ $in_timing = 1;
+ }
+ elsif ($in_timing) {
+ if ($line =~ /^(\S.*\S)\s+(\d+\.\d+)\s*$/) {
+ my ($name, $time) = ($1, $2);
+ my $id = make_id($1, 'timing');
+
+ # Try to column-align the timing numbers. Some test names
+ # will be longer than our max - oh well.
+ my $spaces = 80 - length(unescapeHTML($name));
+ $spaces = 1 if $spaces < 1;
+ $spaces++ if $time < 10;
+ my $spacing = ' ' x $spaces;
+ $line = qq{<a class="timing" href="#t--$id">$name</a>$spacing$time};
+ }
+ else {
+ $in_timing = 0;
+ }
+ }
+
+ #
+ # Ginkgo error reformatting
+ #
+ if ($line =~ /^.{1,4} (Failure|Panic)( in .*)? \[/) {
+ # Begins a block of multiple lines including a stack trace
+ print { $out_fh } "<div class='log-error'>\n";
+ $in_failure = 1;
+ }
+ elsif ($line =~ /^-----------/) {
+ if ($in_failure) {
+ # Ends a stack trace block
+ $in_failure = 0;
+ print { $out_fh } "</div>\n";
+ }
+ $after_divider = 1;
+
+ print { $out_fh } "</pre>\n<hr />\n<pre>\n";
+ # Always show timestamp at start of each new test
+ $previous_timestamp = '';
+ next LINE;
+ }
+ elsif ($line =~ /^Running:/) {
+ # Highlight the important (non-boilerplate) podman command.
+ # Strip out the global podman options, but show them on hover
+ $line =~ s{(\S+\/podman)((\s+--(root|runroot|runtime|tmpdir|storage-opt|conmon|cgroup-manager|cni-config-dir|storage-driver|events-backend) \S+)*)(.*)}{
+ my ($full_path, $options, $args) = ($1, $2, $5);
+
+ $options =~ s/^\s+//;
+ # Separate each '--foo bar' with newlines for readability
+ $options =~ s/ --/\n--/g;
+ qq{<span title="$full_path"><b>podman</b></span> <span class=\"boring\" title=\"$options\">[options]</span><b>$args</b>};
+ }e;
+ $current_output = '';
+ }
+ # Grrr. 'output:' usually just tells us what we already know.
+ elsif ($line =~ /^output:/) {
+ $current_output =~ s!^\s+|\s+$!!g; # Trim leading/trailing blanks
+ $current_output =~ s/\s+/ /g; # Collapse multiple spaces
+ if ($line eq "output: $current_output" || $line eq 'output: ') {
+ next LINE;
+ }
+ }
+ elsif ($line =~ /^Error:/ || $line =~ / level=(warning|error) /) {
+ $line = "<span class='log-warn'>" . $line . "</span>";
+ }
+ else {
+ $current_output .= ' ' . $line;
+ }
+
+
+ # Two lines after each divider, there's a test name. Make it
+ # an anchor so we can link to it later.
+ if ($after_divider++ == 2) {
+ # Sigh. There is no actual marker. Assume that anything with
+ ## two leading spaces then alpha (not slashes) is a test name.
+ if ($line =~ /^ [a-zA-Z]/) {
+ my $id = make_id($line, 'anchor');
+
+ $line = "<a name='t--$id'><h2>$line</h2></a>";
+ }
+ }
+
+ # Failure name corresponds to a previously-seen block.
+ ## FIXME: sometimes there are three failures with the same name.
+ ## ...I have no idea why or how to link to the right ones.
+ # 1 2 2 3 3 14 4
+ if ($line =~ /^(\[(Fail|Panic!)\] .* \[(It|BeforeEach)\] )([A-Za-z].*)/) {
+ my ($lhs, $type, $ginkgo_fluff, $testname) = ($1, $2, $3, $4);
+ my $id = make_id($testname, 'link');
+
+ $line = "<b>$lhs<a href='#t--$id'>$testname</a></b>";
+ }
+
+ print { $out_fh } "<span class=\"timestamp\">$timestamp</span>"
+ if $timestamp;
+ print { $out_fh } $line, "\n";
+ }
+
+ my $have_formatted_log; # Set on success
+
+ if ($out_fh) {
+ print { $out_fh } "</pre>\n";
+
+ # Did we find a cirrus task? Link back.
+ if ($cirrus_task) {
+ print { $out_fh } <<"END_HTML";
+<hr />
+<h3>Cirrus <a href="https://cirrus-ci.com/task/$cirrus_task">task $cirrus_task</a></h3>
+END_HTML
+ }
+
+ # FIXME: need a safe way to get TZ
+ printf { $out_fh } <<"END_HTML", scalar(CORE::localtime);
+<hr />
+<small>Processed %s by $ME v$VERSION</small>
+</body>
+</html>
+END_HTML
+
+ if (close $out_fh) {
+ if (rename $out_tmp => $outfile) {
+ $have_formatted_log = 1;
+ }
+ else {
+ warn "$ME: Could not rename $out_tmp: $!\n";
+ }
+ }
+ else {
+ warn "$ME: Error writing $out_tmp: $!\n";
+ }
+ }
+
+ # FIXME: if Cirrus magic envariables are available, write a link to results
+ if ($have_formatted_log && $ENV{CIRRUS_TASK_ID}) {
+ my $URL_BASE = "https://storage.googleapis.com";
+ my $STATIC_MAGIC_BLOB = "cirrus-ci-5385732420009984-fcae48";
+ my $ARTIFACT_NAME = "html";
+
+ my $URL = "${URL_BASE}/${STATIC_MAGIC_BLOB}/artifacts/$ENV{CIRRUS_REPO_FULL_NAME}/$ENV{CIRRUS_TASK_ID}/${ARTIFACT_NAME}/${outfile}";
+
+ print "\n\nAnnotated results:\n $URL\n";
+ }
+}
+
+
+#############
+# make_id # Given a test name, generate an anchor link name
+#############
+sub make_id {
+ my $name = shift; # in: test title
+ my $type = shift; # in: differentiator (anchor, link)
+
+ state %counter;
+
+ $name =~ s/^\s+|\s+$//g; # strip leading/trailing whitespace
+ $name =~ s/[^a-zA-Z0-9_-]/-/g; # Convert non-alphanumeric to dash
+
+ # Keep a running tally of how many times we've seen this identifier
+ # for this given type! This lets us cross-match, in the bottom of
+ # the page, the first/second/third failure of a given test.
+ $name .= "--" . ++$counter{$type}{$name};
+
+ $name;
+}
+
+
+
+sub escapeHTML {
+ my $s = shift;
+
+ state %chars;
+ %chars = ('&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quot;', "'" => '&#39;')
+ if keys(%chars) == 0;
+ my $class = join('', sort keys %chars);
+ $s =~ s/([$class])/$chars{$1}/ge;
+
+ return $s;
+}
+
+sub unescapeHTML {
+ my $s = shift;
+
+ # We don't actually care about the character, only its length
+ $s =~ s/\&\#?[a-z0-9]+;/./g;
+
+ return $s;
+}
+
+
+1;
diff --git a/docs/source/markdown/podman-container-cleanup.1.md b/docs/source/markdown/podman-container-cleanup.1.md
index 69e21ce9f..86e6b4316 100644
--- a/docs/source/markdown/podman-container-cleanup.1.md
+++ b/docs/source/markdown/podman-container-cleanup.1.md
@@ -22,6 +22,14 @@ to run containers such as CRI-O, the last started container could be from either
The latest option is not supported on the remote client.
+**--rm**
+
+After cleanup, remove the container entirely.
+
+**--rmi**
+
+After cleanup, remove the image entirely.
+
## EXAMPLE
`podman container cleanup mywebserver`
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 220b32a46..bc99a83ca 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -689,6 +689,11 @@ Note that the container will not be removed when it could not be created or
started successfully. This allows the user to inspect the container after
failure.
+**--rmi**=*true|false*
+
+After exit of the container, remove the image unless another
+container is using it. The default is *false*.
+
**--rootfs**
If specified, the first argument refers to an exploded container on the file system.
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index a5e668b04..a5242270e 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -1112,6 +1112,15 @@ func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.Cle
} else {
failures[ctr.ID()] = err
}
+
+ if cli.RemoveImage {
+ _, imageName := ctr.Image()
+ if err := removeContainerImage(ctx, ctr, r); err != nil {
+ failures[imageName] = err
+ } else {
+ ok = append(ok, imageName)
+ }
+ }
}
return ok, failures, nil
}
@@ -1131,6 +1140,16 @@ func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *Local
return nil
}
+func removeContainerImage(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error {
+ _, imageName := ctr.Image()
+ ctrImage, err := runtime.NewImageFromLocal(imageName)
+ if err != nil {
+ return err
+ }
+ _, err = runtime.RemoveImage(ctx, ctrImage, false)
+ return err
+}
+
// Port displays port information about existing containers
func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
var (
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 1d9633bb3..9b2255d61 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -157,6 +157,7 @@ type CreateConfig struct {
Resources CreateResourceConfig
RestartPolicy string
Rm bool //rm
+ Rmi bool //rmi
StopSignal syscall.Signal // stop-signal
StopTimeout uint // stop-timeout
Systemd bool
@@ -234,6 +235,10 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err
command = append(command, "--rm")
}
+ if c.Rmi {
+ command = append(command, "--rmi")
+ }
+
return command, nil
}
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index a69e8cc99..3a5d5a398 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -22,7 +22,7 @@ t GET libpod/containers/json?all=true 200 \
.[0].Id~[0-9a-f]\\{12\\} \
.[0].Image=$IMAGE \
.[0].Command[0]="true" \
- .[0].State=exited \
+ .[0].State~\\\(exited\\\|stopped\\\) \
.[0].ExitCode=0 \
.[0].IsInfra=false
diff --git a/test/apiv2/22-stop.at b/test/apiv2/22-stop.at
new file mode 100644
index 000000000..11318ca81
--- /dev/null
+++ b/test/apiv2/22-stop.at
@@ -0,0 +1,24 @@
+# -*- sh -*-
+#
+# test 'stop' endpoints
+#
+
+podman pull $IMAGE &>/dev/null
+
+# stop, by name
+podman run -dt --name mytop $IMAGE top &>/dev/null
+
+t GET libpod/containers/mytop/json 200 .State.Status=running
+t POST libpod/containers/mytop/stop "" 204
+t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\)
+t DELETE libpod/containers/mytop 204
+
+# stop, by ID
+# Remember that podman() hides all output; we need to get our CID via inspect
+podman run -dt --name mytop $IMAGE top
+
+t GET libpod/containers/mytop/json 200 .State.Status=running
+cid=$(jq -r .Id <<<"$output")
+t POST libpod/containers/$cid/stop "" 204
+t GET libpod/containers/mytop/json 200 .State.Status~\\\(exited\\\|stopped\\\)
+t DELETE libpod/containers/mytop 204
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index 43f08bf03..dc5e91c72 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -122,6 +122,8 @@ func populateCache(podman *PodmanTestIntegration) {
for _, image := range CACHE_IMAGES {
podman.RestoreArtifactToCache(image)
}
+ // logformatter uses this to recognize the first test
+ fmt.Printf("-----------------------------\n")
}
func removeCache() {
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index f1e9776c1..b89c76981 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -136,4 +136,21 @@ echo $rand | 0 | $rand
run_podman rmi busybox
}
+# 'run --rmi' deletes the image in the end unless it's used by another container.
+@test "podman run --rmi - remove image" {
+ skip_if_remote "podman-remote does not emit 'Trying to pull' msgs"
+ run_podman 0 run --rmi --rm redis /bin/true
+ run_podman 1 image exists redis
+}
+
+
+@test "podman run --rmi - not remove image" {
+ skip_if_remote "podman-remote does not emit 'Trying to pull' msgs"
+ run_podman run redis /bin/true
+ run_podman images | grep redis
+ run_podman run --rmi --rm redis /bin/true
+ run_podman images | grep redis
+ run_podman 0 rm -a
+}
+
# vim: filetype=sh