summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml14
-rw-r--r--cmd/podman/common/completion.go144
-rw-r--r--cmd/podman/common/completion_test.go72
-rw-r--r--docs/source/markdown/podman-create.1.md2
-rw-r--r--docs/source/markdown/podman-play-kube.1.md2
-rw-r--r--docs/source/markdown/podman-run.1.md2
-rw-r--r--docs/tutorials/podman-for-windows.md6
-rw-r--r--libpod/boltdb_state.go36
-rw-r--r--libpod/define/container.go2
-rw-r--r--libpod/diff.go23
-rw-r--r--libpod/events.go4
-rw-r--r--libpod/events/events.go2
-rw-r--r--libpod/runtime.go2
-rw-r--r--libpod/runtime_renumber.go2
-rw-r--r--pkg/autoupdate/autoupdate.go3
-rw-r--r--pkg/specgen/generate/kube/kube.go16
-rw-r--r--pkg/specgen/generate/kube/volume.go31
-rw-r--r--pkg/specgen/generate/oci.go2
-rw-r--r--pkg/specgen/generate/storage.go6
-rw-r--r--test/e2e/play_kube_test.go147
-rw-r--r--test/e2e/run_test.go8
-rw-r--r--test/system/255-auto-update.bats12
22 files changed, 455 insertions, 83 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index ee0131279..fe00974b3 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -697,7 +697,7 @@ image_build_task: &image-build
# this task to a specific Cirrus-Cron entry with this name.
only_if: $CIRRUS_CRON == 'multiarch'
depends_on:
- - ext_svc_check
+ - build
timeout_in: 120m # emulation is sssllllooooowwww
gce_instance:
<<: *standardvm
@@ -714,22 +714,24 @@ image_build_task: &image-build
- env:
CTXDIR: contrib/hello
env:
+ DISTRO_NV: "${FEDORA_NAME}" # Required for repo cache extraction
PODMAN_USERNAME: ENCRYPTED[b9f0f2550029dd2196e086d9dd6c2d1fec7e328630b15990d9bb610f9fcccb5baab8b64a8c3e72b0c1d0f5917cf65aa1]
PODMAN_PASSWORD: ENCRYPTED[e3444f6072853f0c8db7f964ead5e2204116af485469fa0de367f26b9316b460fd842a9882f552b9e9a83bbaf650d8b4]
CONTAINERS_USERNAME: ENCRYPTED[54a372d5f22f424173c114c6fb25c3214956cad323d5b285c7393a71041884ce96471d0ff733774e5dab9fa5a3c8795c]
CONTAINERS_PASSWORD: ENCRYPTED[4ecc3fb534935095a99fb1f2e320ac6bc87f3e7e186746e41cbcc4b5f5379a014b9fc8cc90e1f3d5abdbaf31580a4ab9]
- clone_script: &noop mkdir -p $CIRRUS_WORKING_DIR
- script:
+ main_script:
- set -a; source /etc/automation_environment; set +a
- main.sh $CIRRUS_REPO_CLONE_URL $CTXDIR
test_image_build_task:
<<: *image-build
- # Allow this to run inside a PR w/ [CI:BUILD]
- only_if: $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*'
+ alias: test_image_build
+ # Allow this to run inside a PR w/ [CI:BUILD] only.
+ only_if: $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE =~ '.*CI:BUILD.*'
# This takes a LONG time, only run when requested. N/B: Any task
# made to depend on this one will block FOREVER unless triggered.
+ # DO NOT ADD THIS TASK AS DEPENDENCY FOR `success_task`.
trigger_type: manual
# Overwrite all 'env', don't push anything, just do the build.
env:
@@ -758,7 +760,7 @@ meta_task:
GCPJSON: ENCRYPTED[3a198350077849c8df14b723c0f4c9fece9ebe6408d35982e7adf2105a33f8e0e166ed3ed614875a0887e1af2b8775f4]
GCPNAME: ENCRYPTED[2f9738ef295a706f66a13891b40e8eaa92a89e0e87faf8bed66c41eca72bf76cfd190a6f2d0e8444c631fdf15ed32ef6]
GCPPROJECT: libpod-218412
- clone_script: *noop
+ clone_script: &noop mkdir -p $CIRRUS_WORKING_DIR
script: /usr/local/bin/entrypoint.sh
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index 58dff3578..099fc267e 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"reflect"
+ "strconv"
"strings"
"github.com/containers/common/libnetwork/types"
@@ -963,9 +964,22 @@ func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete strin
return append(networks, suggestions...), dir
}
+type formatSuggestion struct {
+ fieldname string
+ suffix string
+}
+
+func convertFormatSuggestions(suggestions []formatSuggestion) []string {
+ completions := make([]string, 0, len(suggestions))
+ for _, f := range suggestions {
+ completions = append(completions, f.fieldname+f.suffix)
+ }
+ return completions
+}
+
// AutocompleteFormat - Autocomplete json or a given struct to use for a go template.
// The input can be nil, In this case only json will be autocompleted.
-// This function will only work for structs other types are not supported.
+// This function will only work for pointer to structs other types are not supported.
// When "{{." is typed the field and method names of the given struct will be completed.
// This also works recursive for nested structs.
func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -994,6 +1008,12 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t
// split this into it struct field names
fields := strings.Split(field[len(field)-1], ".")
f := reflect.ValueOf(o)
+ if f.Kind() != reflect.Ptr {
+ // We panic here to make sure that all callers pass the value by reference.
+ // If someone passes a by value then all podman commands will panic since
+ // this function is run at init time.
+ panic("AutocompleteFormat: passed value must be a pointer to a struct")
+ }
for i := 1; i < len(fields); i++ {
// last field get all names to suggest
if i == len(fields)-1 {
@@ -1002,61 +1022,83 @@ func AutocompleteFormat(o interface{}) func(cmd *cobra.Command, args []string, t
toCompArr := strings.Split(toComplete, ".")
toCompArr[len(toCompArr)-1] = ""
toComplete = strings.Join(toCompArr, ".")
- return prefixSlice(toComplete, suggestions), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
+ return prefixSlice(toComplete, convertFormatSuggestions(suggestions)), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
}
- val := getActualStructType(f)
- if val == nil {
- // no struct return nothing to complete
+ // first follow pointer and create element when it is nil
+ f = actualReflectValue(f)
+ switch f.Kind() {
+ case reflect.Struct:
+ for j := 0; j < f.NumField(); j++ {
+ field := f.Type().Field(j)
+ // ok this is a bit weird but when we have an embedded nil struct
+ // calling FieldByName on a name which is present on this struct will panic
+ // Therefore we have to init them (non nil ptr), https://github.com/containers/podman/issues/14223
+ if field.Anonymous && f.Field(j).Type().Kind() == reflect.Ptr {
+ f.Field(j).Set(reflect.New(f.Field(j).Type().Elem()))
+ }
+ }
+ // set the next struct field
+ f = f.FieldByName(fields[i])
+ case reflect.Map:
+ rtype := f.Type().Elem()
+ if rtype.Kind() == reflect.Ptr {
+ rtype = rtype.Elem()
+ }
+ f = reflect.New(rtype)
+ case reflect.Func:
+ if f.Type().NumOut() != 1 {
+ // unsupported type return nothing
+ return nil, cobra.ShellCompDirectiveNoFileComp
+ }
+ f = reflect.New(f.Type().Out(0))
+ default:
+ // unsupported type return nothing
return nil, cobra.ShellCompDirectiveNoFileComp
}
- f = *val
-
- // set the next struct field
- f = f.FieldByName(fields[i])
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
}
-// getActualStructType take the value and check if it is a struct,
+// actualReflectValue takes the value,
// if it is pointer it will dereference it and when it is nil,
-// it will create a new value from it to get the actual struct
-// returns nil when type is not a struct
-func getActualStructType(f reflect.Value) *reflect.Value {
+// it will create a new value from it
+func actualReflectValue(f reflect.Value) reflect.Value {
// follow the pointer first
if f.Kind() == reflect.Ptr {
// if the pointer is nil we create a new value from the elements type
- // this allows us to follow nil pointers and get the actual struct fields
+ // this allows us to follow nil pointers and get the actual type
if f.IsNil() {
f = reflect.New(f.Type().Elem())
}
f = f.Elem()
}
- // we only support structs
- if f.Kind() != reflect.Struct {
- return nil
- }
- return &f
+ return f
}
// getStructFields reads all struct field names and method names and returns them.
-func getStructFields(f reflect.Value, prefix string) []string {
- var suggestions []string
+func getStructFields(f reflect.Value, prefix string) []formatSuggestion {
+ var suggestions []formatSuggestion
if f.IsValid() {
suggestions = append(suggestions, getMethodNames(f, prefix)...)
}
- val := getActualStructType(f)
- if val == nil {
- // no struct return nothing to complete
+ f = actualReflectValue(f)
+ // we only support structs
+ if f.Kind() != reflect.Struct {
return suggestions
}
- f = *val
+ var anonymous []formatSuggestion
// loop over all field names
for j := 0; j < f.NumField(); j++ {
field := f.Type().Field(j)
+ // check if struct field is not exported, templates only use exported fields
+ // PkgPath is always empty for exported fields
+ if field.PkgPath != "" {
+ continue
+ }
fname := field.Name
suffix := "}}"
kind := field.Type.Kind()
@@ -1065,27 +1107,63 @@ func getStructFields(f reflect.Value, prefix string) []string {
kind = field.Type.Elem().Kind()
}
// when we have a nested struct do not append braces instead append a dot
- if kind == reflect.Struct {
+ if kind == reflect.Struct || kind == reflect.Map {
suffix = "."
}
// if field is anonymous add the child fields as well
if field.Anonymous {
- suggestions = append(suggestions, getStructFields(f.Field(j), prefix)...)
- } else if strings.HasPrefix(fname, prefix) {
+ anonymous = append(anonymous, getStructFields(f.Field(j), prefix)...)
+ }
+ if strings.HasPrefix(fname, prefix) {
// add field name with suffix
- suggestions = append(suggestions, fname+suffix)
+ suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix})
+ }
+ }
+outer:
+ for _, ano := range anonymous {
+ // we should only add anonymous child fields if they are not already present.
+ for _, sug := range suggestions {
+ if ano.fieldname == sug.fieldname {
+ continue outer
+ }
}
+ suggestions = append(suggestions, ano)
}
return suggestions
}
-func getMethodNames(f reflect.Value, prefix string) []string {
- suggestions := make([]string, 0, f.NumMethod())
+func getMethodNames(f reflect.Value, prefix string) []formatSuggestion {
+ suggestions := make([]formatSuggestion, 0, f.NumMethod())
for j := 0; j < f.NumMethod(); j++ {
- fname := f.Type().Method(j).Name
+ method := f.Type().Method(j)
+ // in a template we can only run functions with one return value
+ if method.Func.Type().NumOut() != 1 {
+ continue
+ }
+ // when we have a nested struct do not append braces instead append a dot
+ kind := method.Func.Type().Out(0).Kind()
+ suffix := "}}"
+ if kind == reflect.Struct || kind == reflect.Map {
+ suffix = "."
+ }
+ // From a template users POV it is not importent when the use a struct field or method.
+ // They only notice the difference when the function requires arguments.
+ // So lets be nice and let the user know that this method requires arguments via the help text.
+ // Note since this is actually a method on a type the first argument is always fix so we should skip it.
+ num := method.Func.Type().NumIn() - 1
+ if num > 0 {
+ // everything after tab will the completion scripts show as help when enabled
+ // overwrite the suffix because it expects the args
+ suffix = "\tThis is a function and requires " + strconv.Itoa(num) + " argument"
+ if num > 1 {
+ // add plural s
+ suffix += "s"
+ }
+ }
+ fname := method.Name
if strings.HasPrefix(fname, prefix) {
// add method name with closing braces
- suggestions = append(suggestions, fname+"}}")
+ suggestions = append(suggestions, formatSuggestion{fieldname: fname, suffix: suffix})
}
}
return suggestions
diff --git a/cmd/podman/common/completion_test.go b/cmd/podman/common/completion_test.go
index 13f45a662..ae23b02e2 100644
--- a/cmd/podman/common/completion_test.go
+++ b/cmd/podman/common/completion_test.go
@@ -14,11 +14,29 @@ type Car struct {
HP *int
Displacement int
}
- Extras map[string]string
+ Extras map[string]Extra
+ // also ensure it will work with pointers
+ Extras2 map[string]*Extra
+}
+
+type Extra struct {
+ Name1 string
+ Name2 string
}
type Anonymous struct {
Hello string
+ // The name should match the testStruct Name below. This is used to make
+ // sure the logic uses the actual struct fields before the embedded ones.
+ Name struct {
+ Suffix string
+ Prefix string
+ }
+}
+
+// The name should match the testStruct Age name below.
+func (a Anonymous) Age() int {
+ return 0
}
func (c Car) Type() string {
@@ -31,6 +49,20 @@ func (c *Car) Color() string {
return ""
}
+// This is for reflect testing required.
+// nolint:unused
+func (c Car) internal() int {
+ return 0
+}
+
+func (c Car) TwoOut() (string, string) {
+ return "", ""
+}
+
+func (c Car) Struct() Car {
+ return Car{}
+}
+
func TestAutocompleteFormat(t *testing.T) {
testStruct := struct {
Name string
@@ -38,10 +70,10 @@ func TestAutocompleteFormat(t *testing.T) {
Car *Car
Car2 *Car
*Anonymous
+ private int
}{}
testStruct.Car = &Car{}
- testStruct.Car.Extras = map[string]string{"test": "1"}
tests := []struct {
name string
@@ -76,17 +108,17 @@ func TestAutocompleteFormat(t *testing.T) {
{
"invalid completion",
"{{ ..",
- nil,
+ []string{},
},
{
"fist level struct field name",
"{{.",
- []string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Hello}}"},
+ []string{"{{.Name}}", "{{.Age}}", "{{.Car.", "{{.Car2.", "{{.Anonymous.", "{{.Hello}}"},
},
{
"fist level struct field name",
"{{ .",
- []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Hello}}"},
+ []string{"{{ .Name}}", "{{ .Age}}", "{{ .Car.", "{{ .Car2.", "{{ .Anonymous.", "{{ .Hello}}"},
},
{
"fist level struct field name",
@@ -96,7 +128,7 @@ func TestAutocompleteFormat(t *testing.T) {
{
"second level struct field name",
"{{ .Car.",
- []string{"{{ .Car.Color}}", "{{ .Car.Type}}", "{{ .Car.Brand}}", "{{ .Car.Stats.", "{{ .Car.Extras}}"},
+ []string{"{{ .Car.Color}}", "{{ .Car.Struct.", "{{ .Car.Type}}", "{{ .Car.Brand}}", "{{ .Car.Stats.", "{{ .Car.Extras.", "{{ .Car.Extras2."},
},
{
"second level struct field name",
@@ -106,7 +138,7 @@ func TestAutocompleteFormat(t *testing.T) {
{
"second level nil struct field name",
"{{ .Car2.",
- []string{"{{ .Car2.Color}}", "{{ .Car2.Type}}", "{{ .Car2.Brand}}", "{{ .Car2.Stats.", "{{ .Car2.Extras}}"},
+ []string{"{{ .Car2.Color}}", "{{ .Car2.Struct.", "{{ .Car2.Type}}", "{{ .Car2.Brand}}", "{{ .Car2.Stats.", "{{ .Car2.Extras.", "{{ .Car2.Extras2."},
},
{
"three level struct field name",
@@ -126,28 +158,44 @@ func TestAutocompleteFormat(t *testing.T) {
{
"invalid field name",
"{{ .Ca.B",
- nil,
+ []string{},
},
{
"map key names don't work",
"{{ .Car.Extras.",
- nil,
+ []string{},
+ },
+ {
+ "map values work",
+ "{{ .Car.Extras.somekey.",
+ []string{"{{ .Car.Extras.somekey.Name1}}", "{{ .Car.Extras.somekey.Name2}}"},
+ },
+ {
+ "map values work with ptr",
+ "{{ .Car.Extras2.somekey.",
+ []string{"{{ .Car.Extras2.somekey.Name1}}", "{{ .Car.Extras2.somekey.Name2}}"},
},
{
"two variables struct field name",
"{{ .Car.Brand }} {{ .Car.",
- []string{"{{ .Car.Brand }} {{ .Car.Color}}", "{{ .Car.Brand }} {{ .Car.Type}}", "{{ .Car.Brand }} {{ .Car.Brand}}",
- "{{ .Car.Brand }} {{ .Car.Stats.", "{{ .Car.Brand }} {{ .Car.Extras}}"},
+ []string{"{{ .Car.Brand }} {{ .Car.Color}}", "{{ .Car.Brand }} {{ .Car.Struct.", "{{ .Car.Brand }} {{ .Car.Type}}",
+ "{{ .Car.Brand }} {{ .Car.Brand}}", "{{ .Car.Brand }} {{ .Car.Stats.", "{{ .Car.Brand }} {{ .Car.Extras.",
+ "{{ .Car.Brand }} {{ .Car.Extras2."},
},
{
"only dot without variable",
".",
nil,
},
+ {
+ "access embedded nil struct field",
+ "{{.Hello.",
+ []string{},
+ },
}
for _, test := range tests {
- completion, directive := common.AutocompleteFormat(testStruct)(nil, nil, test.toComplete)
+ completion, directive := common.AutocompleteFormat(&testStruct)(nil, nil, test.toComplete)
// directive should always be greater than ShellCompDirectiveNoFileComp
assert.GreaterOrEqual(t, directive, cobra.ShellCompDirectiveNoFileComp, "unexpected ShellCompDirective")
assert.Equal(t, test.expected, completion, test.name)
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index c63e8814b..009209343 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -460,6 +460,8 @@ content that disappears when the container is stopped.
#### **--init**
Run an init inside the container that forwards signals and reaps processes.
+The container-init binary is mounted at `/run/podman-init`.
+Mounting over `/run` will hence break container execution.
#### **--init-ctr**=*type* (pods only)
diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md
index 5c4bdc8c4..08bb2a5bc 100644
--- a/docs/source/markdown/podman-play-kube.1.md
+++ b/docs/source/markdown/podman-play-kube.1.md
@@ -20,7 +20,7 @@ Currently, the supported Kubernetes kinds are:
`Kubernetes Pods or Deployments`
-Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, and *Socket* subtypes are supported. The *CharDevice* and *BlockDevice* subtypes are not supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
+Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume.
Note: When playing a kube YAML with init containers, the init container will be created with init type value `always`.
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 9d9394020..a16ee9394 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -498,6 +498,8 @@ content that disappears when the container is stopped.
#### **--init**
Run an init inside the container that forwards signals and reaps processes.
+The container-init binary is mounted at `/run/podman-init`.
+Mounting over `/run` will hence break container execution.
#### **--init-path**=*path*
diff --git a/docs/tutorials/podman-for-windows.md b/docs/tutorials/podman-for-windows.md
index bb9674774..4e929a14a 100644
--- a/docs/tutorials/podman-for-windows.md
+++ b/docs/tutorials/podman-for-windows.md
@@ -233,15 +233,15 @@ Linux container. This supports several notation schemes, including:
Windows Style Paths:
-`podman run -it c:\Users\User\myfolder:/myfolder ubi8-micro ls /myfolder`
+`podman run --rm -v c:\Users\User\myfolder:/myfolder ubi8-micro ls /myfolder`
Unixy Windows Paths:
-`podman run -it /c/Users/User/myfolder:/myfolder ubi8-micro ls /myfolder`
+`podman run --rm -v /c/Users/User/myfolder:/myfolder ubi8-micro ls /myfolder`
Linux paths local to the WSL filesystem:
-`podman run -it /var/myfolder:/myfolder ubi-micro ls /myfolder`
+`podman run --rm -v /var/myfolder:/myfolder ubi-micro ls /myfolder`
All of the above conventions work, whether running on a Windows prompt or the
WSL Linux shell. Although when using Windows paths on Linux, appropriately quote
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index 9745121c7..c3db6152a 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -162,6 +162,11 @@ func (s *BoltState) Refresh() error {
return err
}
+ namesBucket, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+
ctrsBucket, err := getCtrBucket(tx)
if err != nil {
return err
@@ -192,6 +197,7 @@ func (s *BoltState) Refresh() error {
// PID, mountpoint, and state for all of them
// Then save the modified state
// Also clear all network namespaces
+ toRemoveIDs := []string{}
err = idBucket.ForEach(func(id, name []byte) error {
ctrBkt := ctrsBucket.Bucket(id)
if ctrBkt == nil {
@@ -199,8 +205,16 @@ func (s *BoltState) Refresh() error {
podBkt := podsBucket.Bucket(id)
if podBkt == nil {
// This is neither a pod nor a container
- // Error out on the dangling ID
- return errors.Wrapf(define.ErrInternal, "id %s is not a pod or a container", string(id))
+ // Something is seriously wrong, but
+ // continue on and try to clean up the
+ // state and become consistent.
+ // Just note what needs to be removed
+ // for now - ForEach says you shouldn't
+ // remove things from the table during
+ // it.
+ logrus.Errorf("Database issue: dangling ID %s found (not a pod or container) - removing", string(id))
+ toRemoveIDs = append(toRemoveIDs, string(id))
+ return nil
}
// Get the state
@@ -285,6 +299,24 @@ func (s *BoltState) Refresh() error {
return err
}
+ // Remove dangling IDs.
+ for _, id := range toRemoveIDs {
+ // Look up the ID to see if we also have a dangling name
+ // in the DB.
+ name := idBucket.Get([]byte(id))
+ if name != nil {
+ if testID := namesBucket.Get(name); testID != nil {
+ logrus.Infof("Found dangling name %s (ID %s) in database", string(name), id)
+ if err := namesBucket.Delete(name); err != nil {
+ return errors.Wrapf(err, "error removing dangling name %s (ID %s) from database", string(name), id)
+ }
+ }
+ }
+ if err := idBucket.Delete([]byte(id)); err != nil {
+ return errors.Wrapf(err, "error removing dangling ID %s from database", id)
+ }
+ }
+
// Now refresh volumes
err = allVolsBucket.ForEach(func(id, name []byte) error {
dbVol := volBucket.Bucket(id)
diff --git a/libpod/define/container.go b/libpod/define/container.go
index bb44a6a4a..ba939578f 100644
--- a/libpod/define/container.go
+++ b/libpod/define/container.go
@@ -35,4 +35,6 @@ const (
// OneShotInitContainer is a container that only runs as init once
// and is then deleted.
OneShotInitContainer = "once"
+ // ContainerInitPath is the default path of the mounted container init.
+ ContainerInitPath = "/run/podman-init"
)
diff --git a/libpod/diff.go b/libpod/diff.go
index 794b26b48..86fa063ec 100644
--- a/libpod/diff.go
+++ b/libpod/diff.go
@@ -8,17 +8,18 @@ import (
)
var initInodes = map[string]bool{
- "/dev": true,
- "/etc/hostname": true,
- "/etc/hosts": true,
- "/etc/resolv.conf": true,
- "/proc": true,
- "/run": true,
- "/run/notify": true,
- "/run/.containerenv": true,
- "/run/secrets": true,
- "/sys": true,
- "/etc/mtab": true,
+ "/dev": true,
+ "/etc/hostname": true,
+ "/etc/hosts": true,
+ "/etc/resolv.conf": true,
+ "/proc": true,
+ "/run": true,
+ "/run/notify": true,
+ "/run/.containerenv": true,
+ "/run/secrets": true,
+ define.ContainerInitPath: true,
+ "/sys": true,
+ "/etc/mtab": true,
}
// GetDiff returns the differences between the two images, layers, or containers
diff --git a/libpod/events.go b/libpod/events.go
index 39f5786a4..f09d8402a 100644
--- a/libpod/events.go
+++ b/libpod/events.go
@@ -89,8 +89,8 @@ func (p *Pod) newPodEvent(status events.Status) {
}
}
-// newSystemEvent creates a new event for libpod as a whole.
-func (r *Runtime) newSystemEvent(status events.Status) {
+// NewSystemEvent creates a new event for libpod as a whole.
+func (r *Runtime) NewSystemEvent(status events.Status) {
e := events.NewEvent(status)
e.Type = events.System
diff --git a/libpod/events/events.go b/libpod/events/events.go
index 04417fd8d..e83c2efee 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -150,6 +150,8 @@ func StringToStatus(name string) (Status, error) {
switch name {
case Attach.String():
return Attach, nil
+ case AutoUpdate.String():
+ return AutoUpdate, nil
case Build.String():
return Build, nil
case Checkpoint.String():
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 58f20ef5b..4efa7b8e8 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -930,7 +930,7 @@ func (r *Runtime) refresh(alivePath string) error {
}
defer file.Close()
- r.newSystemEvent(events.Refresh)
+ r.NewSystemEvent(events.Refresh)
return nil
}
diff --git a/libpod/runtime_renumber.go b/libpod/runtime_renumber.go
index 17e1d97e5..db055f40b 100644
--- a/libpod/runtime_renumber.go
+++ b/libpod/runtime_renumber.go
@@ -71,7 +71,7 @@ func (r *Runtime) renumberLocks() error {
}
}
- r.newSystemEvent(events.Renumber)
+ r.NewSystemEvent(events.Renumber)
return nil
}
diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go
index ee530528e..0c795faed 100644
--- a/pkg/autoupdate/autoupdate.go
+++ b/pkg/autoupdate/autoupdate.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/podman/v4/libpod"
"github.com/containers/podman/v4/libpod/define"
+ "github.com/containers/podman/v4/libpod/events"
"github.com/containers/podman/v4/pkg/domain/entities"
"github.com/containers/podman/v4/pkg/systemd"
systemdDefine "github.com/containers/podman/v4/pkg/systemd/define"
@@ -142,6 +143,8 @@ func AutoUpdate(ctx context.Context, runtime *libpod.Runtime, options entities.A
}
defer conn.Close()
+ runtime.NewSystemEvent(events.AutoUpdate)
+
// Update all images/container according to their auto-update policy.
var allReports []*entities.AutoUpdateReport
updatedRawImages := make(map[string]bool)
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
index e4c149abf..f37d79798 100644
--- a/pkg/specgen/generate/kube/kube.go
+++ b/pkg/specgen/generate/kube/kube.go
@@ -381,6 +381,22 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Options: options,
}
s.Volumes = append(s.Volumes, &cmVolume)
+ case KubeVolumeTypeCharDevice:
+ // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
+ // The type is here just to improve readability as it is not taken into account when the actual device is created.
+ device := spec.LinuxDevice{
+ Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath),
+ Type: "c",
+ }
+ s.Devices = append(s.Devices, device)
+ case KubeVolumeTypeBlockDevice:
+ // We are setting the path as hostPath:mountPath to comply with pkg/specgen/generate.DeviceFromPath.
+ // The type is here just to improve readability as it is not taken into account when the actual device is created.
+ device := spec.LinuxDevice{
+ Path: fmt.Sprintf("%s:%s", volumeSource.Source, volume.MountPath),
+ Type: "b",
+ }
+ s.Devices = append(s.Devices, device)
default:
return nil, errors.Errorf("Unsupported volume source type")
}
diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go
index 27881e77a..1d6d49b9d 100644
--- a/pkg/specgen/generate/kube/volume.go
+++ b/pkg/specgen/generate/kube/volume.go
@@ -22,8 +22,10 @@ type KubeVolumeType int
const (
KubeVolumeTypeBindMount KubeVolumeType = iota
- KubeVolumeTypeNamed KubeVolumeType = iota
- KubeVolumeTypeConfigMap KubeVolumeType = iota
+ KubeVolumeTypeNamed
+ KubeVolumeTypeConfigMap
+ KubeVolumeTypeBlockDevice
+ KubeVolumeTypeCharDevice
)
//nolint:revive
@@ -78,7 +80,30 @@ func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error)
if st.Mode()&os.ModeSocket != os.ModeSocket {
return nil, errors.Errorf("checking HostPathSocket: path %s is not a socket", hostPath.Path)
}
-
+ case v1.HostPathBlockDev:
+ dev, err := os.Stat(hostPath.Path)
+ if err != nil {
+ return nil, errors.Wrap(err, "error checking HostPathBlockDevice")
+ }
+ if dev.Mode()&os.ModeCharDevice == os.ModeCharDevice {
+ return nil, errors.Errorf("checking HostPathDevice: path %s is not a block device", hostPath.Path)
+ }
+ return &KubeVolume{
+ Type: KubeVolumeTypeBlockDevice,
+ Source: hostPath.Path,
+ }, nil
+ case v1.HostPathCharDev:
+ dev, err := os.Stat(hostPath.Path)
+ if err != nil {
+ return nil, errors.Wrap(err, "error checking HostPathCharDevice")
+ }
+ if dev.Mode()&os.ModeCharDevice != os.ModeCharDevice {
+ return nil, errors.Errorf("checking HostPathCharDevice: path %s is not a character device", hostPath.Path)
+ }
+ return &KubeVolume{
+ Type: KubeVolumeTypeCharDevice,
+ Source: hostPath.Path,
+ }, nil
case v1.HostPathDirectory:
case v1.HostPathFile:
case v1.HostPathUnset:
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 081df0441..dda2de6e4 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -128,7 +128,7 @@ func makeCommand(s *specgen.SpecGenerator, imageData *libimage.ImageData, rtc *c
if initPath == "" {
return nil, errors.Errorf("no path to init binary found but container requested an init")
}
- finalCommand = append([]string{"/dev/init", "--"}, finalCommand...)
+ finalCommand = append([]string{define.ContainerInitPath, "--"}, finalCommand...)
}
return finalCommand, nil
diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go
index f30fc4671..0a4d03780 100644
--- a/pkg/specgen/generate/storage.go
+++ b/pkg/specgen/generate/storage.go
@@ -20,9 +20,7 @@ import (
"github.com/sirupsen/logrus"
)
-var (
- errDuplicateDest = errors.Errorf("duplicate mount destination")
-)
+var errDuplicateDest = errors.Errorf("duplicate mount destination")
// Produce final mounts and named volumes for a container
func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *libimage.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) {
@@ -359,7 +357,7 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s
// This does *NOT* modify the container command - that must be done elsewhere.
func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) {
mount := spec.Mount{
- Destination: "/dev/init",
+ Destination: define.ContainerInitPath,
Type: define.TypeBind,
Source: path,
Options: []string{define.TypeBind, "ro"},
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 216c3357c..31044f68b 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -21,6 +21,7 @@ import (
"github.com/containers/podman/v4/pkg/util"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage/pkg/stringid"
+ "github.com/google/uuid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
@@ -3685,4 +3686,150 @@ ENV OPENJ9_JAVA_OPTIONS=%q
Expect(usernsInCtr).Should(Exit(0))
Expect(string(usernsInCtr.Out.Contents())).To(Not(Equal(string(initialUsernsConfig))))
})
+
+ // Check the block devices are exposed inside container
+ It("ddpodman play kube expose block device inside container", func() {
+ SkipIfRootless("It needs root access to create devices")
+
+ // randomize the folder name to avoid error when running tests with multiple nodes
+ uuid, err := uuid.NewUUID()
+ Expect(err).To(BeNil())
+ devFolder := fmt.Sprintf("/dev/foodev%x", uuid[:6])
+ Expect(os.MkdirAll(devFolder, os.ModePerm)).To(BeNil())
+ defer os.RemoveAll(devFolder)
+
+ devicePath := fmt.Sprintf("%s/blockdevice", devFolder)
+ mknod := SystemExec("mknod", []string{devicePath, "b", "7", "0"})
+ mknod.WaitWithDefaultTimeout()
+ Expect(mknod).Should(Exit(0))
+
+ blockVolume := getHostPathVolume("BlockDevice", devicePath)
+
+ pod := getPod(withVolume(blockVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ // Container should be in running state
+ inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect).Should(Exit(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring("running"))
+
+ // Container should have a block device /dev/loop1
+ inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.HostConfig.Devices}}", "testPod-" + defaultCtrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect).Should(Exit(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring(devicePath))
+ })
+
+ // Check the char devices are exposed inside container
+ It("ddpodman play kube expose character device inside container", func() {
+ SkipIfRootless("It needs root access to create devices")
+
+ // randomize the folder name to avoid error when running tests with multiple nodes
+ uuid, err := uuid.NewUUID()
+ Expect(err).To(BeNil())
+ devFolder := fmt.Sprintf("/dev/foodev%x", uuid[:6])
+ Expect(os.MkdirAll(devFolder, os.ModePerm)).To(BeNil())
+ defer os.RemoveAll(devFolder)
+
+ devicePath := fmt.Sprintf("%s/chardevice", devFolder)
+ mknod := SystemExec("mknod", []string{devicePath, "c", "3", "1"})
+ mknod.WaitWithDefaultTimeout()
+ Expect(mknod).Should(Exit(0))
+
+ charVolume := getHostPathVolume("CharDevice", devicePath)
+
+ pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ // Container should be in running state
+ inspect := podmanTest.Podman([]string{"inspect", "--format", "{{.State.Status}}", "testPod-" + defaultCtrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect).Should(Exit(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring("running"))
+
+ // Container should have a block device /dev/loop1
+ inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.HostConfig.Devices}}", "testPod-" + defaultCtrName})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect).Should(Exit(0))
+ Expect(inspect.OutputToString()).To(ContainSubstring(devicePath))
+ })
+
+ It("podman play kube reports error when the device does not exists", func() {
+ SkipIfRootless("It needs root access to create devices")
+
+ devicePath := "/dev/foodevdir/baddevice"
+
+ blockVolume := getHostPathVolume("BlockDevice", devicePath)
+
+ pod := getPod(withVolume(blockVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(125))
+ })
+
+ It("ddpodman play kube reports error when we try to expose char device as block device", func() {
+ SkipIfRootless("It needs root access to create devices")
+
+ // randomize the folder name to avoid error when running tests with multiple nodes
+ uuid, err := uuid.NewUUID()
+ Expect(err).To(BeNil())
+ devFolder := fmt.Sprintf("/dev/foodev%x", uuid[:6])
+ Expect(os.MkdirAll(devFolder, os.ModePerm)).To(BeNil())
+ defer os.RemoveAll(devFolder)
+
+ devicePath := fmt.Sprintf("%s/chardevice", devFolder)
+ mknod := SystemExec("mknod", []string{devicePath, "c", "3", "1"})
+ mknod.WaitWithDefaultTimeout()
+ Expect(mknod).Should(Exit(0))
+
+ charVolume := getHostPathVolume("BlockDevice", devicePath)
+
+ pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(125))
+ })
+
+ It("ddpodman play kube reports error when we try to expose block device as char device", func() {
+ SkipIfRootless("It needs root access to create devices")
+
+ // randomize the folder name to avoid error when running tests with multiple nodes
+ uuid, err := uuid.NewUUID()
+ Expect(err).To(BeNil())
+ devFolder := fmt.Sprintf("/dev/foodev%x", uuid[:6])
+ Expect(os.MkdirAll(devFolder, os.ModePerm)).To(BeNil())
+
+ devicePath := fmt.Sprintf("%s/blockdevice", devFolder)
+ mknod := SystemExec("mknod", []string{devicePath, "b", "7", "0"})
+ mknod.WaitWithDefaultTimeout()
+ Expect(mknod).Should(Exit(0))
+
+ charVolume := getHostPathVolume("CharDevice", devicePath)
+
+ pod := getPod(withVolume(charVolume), withCtr(getCtr(withImage(registry), withCmd(nil), withArg(nil), withVolumeMount(devicePath, false))))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(125))
+ })
+
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 182ae1888..828e92170 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/containers/common/pkg/cgroups"
+ "github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/rootless"
. "github.com/containers/podman/v4/test/utils"
"github.com/containers/storage/pkg/stringid"
@@ -286,19 +287,20 @@ var _ = Describe("Podman run", func() {
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
conData := result.InspectContainerToJSON()
- Expect(conData[0]).To(HaveField("Path", "/dev/init"))
+ Expect(conData[0]).To(HaveField("Path", define.ContainerInitPath))
Expect(conData[0].Config.Annotations).To(HaveKeyWithValue("io.podman.annotations.init", "TRUE"))
})
It("podman run a container with --init and --init-path", func() {
- session := podmanTest.Podman([]string{"run", "--name", "test", "--init", "--init-path", "/usr/libexec/podman/catatonit", ALPINE, "ls"})
+ // Also bind-mount /dev (#14251).
+ session := podmanTest.Podman([]string{"run", "-v", "/dev:/dev", "--name", "test", "--init", "--init-path", "/usr/libexec/podman/catatonit", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
result := podmanTest.Podman([]string{"inspect", "test"})
result.WaitWithDefaultTimeout()
Expect(result).Should(Exit(0))
conData := result.InspectContainerToJSON()
- Expect(conData[0]).To(HaveField("Path", "/dev/init"))
+ Expect(conData[0]).To(HaveField("Path", define.ContainerInitPath))
Expect(conData[0].Config.Annotations).To(HaveKeyWithValue("io.podman.annotations.init", "TRUE"))
})
diff --git a/test/system/255-auto-update.bats b/test/system/255-auto-update.bats
index 6cdae2ada..6cee939fb 100644
--- a/test/system/255-auto-update.bats
+++ b/test/system/255-auto-update.bats
@@ -135,15 +135,27 @@ function _confirm_update() {
# This test can fail in dev. environment because of SELinux.
# quick fix: chcon -t container_runtime_exec_t ./bin/podman
@test "podman auto-update - label io.containers.autoupdate=image" {
+ since=$(date --iso-8601=seconds)
+ run_podman auto-update
+ is "$output" ""
+ run_podman events --filter type=system --since $since --stream=false
+ is "$output" ""
+
generate_service alpine image
_wait_service_ready container-$cname.service
+ since=$(date --iso-8601=seconds)
run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
is "$output" ".*container-$cname.service,quay.io/libpod/alpine:latest,pending,registry.*" "Image update is pending."
+ run_podman events --filter type=system --since $since --stream=false
+ is "$output" ".* system auto-update"
+ since=$(date --iso-8601=seconds)
run_podman auto-update --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
is "$output" "Trying to pull.*" "Image is updated."
is "$output" ".*container-$cname.service,quay.io/libpod/alpine:latest,true,registry.*" "Image is updated."
+ run_podman events --filter type=system --since $since --stream=false
+ is "$output" ".* system auto-update"
_confirm_update $cname $ori_image
}