summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/inspect/inspect.go1
-rw-r--r--pkg/lookup/lookup.go18
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/registries/registries.go37
-rw-r--r--pkg/resolvconf/resolvconf.go12
-rw-r--r--pkg/rootless/rootless_linux.go40
-rw-r--r--pkg/secrets/secrets.go9
-rw-r--r--pkg/spec/createconfig.go12
-rw-r--r--pkg/spec/spec.go3
-rw-r--r--pkg/trust/trust.go250
-rw-r--r--pkg/util/utils.go101
-rw-r--r--pkg/varlinkapi/containers.go49
-rw-r--r--pkg/varlinkapi/containers_create.go9
-rw-r--r--pkg/varlinkapi/images.go144
-rw-r--r--pkg/varlinkapi/mount.go49
-rw-r--r--pkg/varlinkapi/pods.go24
16 files changed, 695 insertions, 70 deletions
diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go
index 62ba53147..5bdcf677f 100644
--- a/pkg/inspect/inspect.go
+++ b/pkg/inspect/inspect.go
@@ -126,6 +126,7 @@ type ImageData struct {
Annotations map[string]string `json:"Annotations"`
ManifestType string `json:"ManifestType"`
User string `json:"User"`
+ History []v1.History `json:"History"`
}
// RootFS holds the root fs information of an image
diff --git a/pkg/lookup/lookup.go b/pkg/lookup/lookup.go
index a9d975b4b..70b97144f 100644
--- a/pkg/lookup/lookup.go
+++ b/pkg/lookup/lookup.go
@@ -99,9 +99,11 @@ func GetContainerGroups(groups []string, containerMount string, override *Overri
return uintgids, nil
}
-// GetUser takes a containermount path and user name or id and returns
+// GetUser takes a containermount path and user name or ID and returns
// a matching User structure from /etc/passwd. If it cannot locate a user
// with the provided information, an ErrNoPasswdEntries is returned.
+// When the provided user name was an ID, a User structure with Uid
+// set is returned along with ErrNoPasswdEntries.
func GetUser(containerMount, userIDorName string) (*user.User, error) {
var inputIsName bool
uid, err := strconv.Atoi(userIDorName)
@@ -124,12 +126,17 @@ func GetUser(containerMount, userIDorName string) (*user.User, error) {
if len(users) > 0 {
return &users[0], nil
}
+ if !inputIsName {
+ return &user.User{Uid: uid}, user.ErrNoPasswdEntries
+ }
return nil, user.ErrNoPasswdEntries
}
-// GetGroup takes ac ontainermount path and a group name or id and returns
-// a match Group struct from /etc/group. if it cannot locate a group,
-// an ErrNoGroupEntries error is returned.
+// GetGroup takes a containermount path and a group name or ID and returns
+// a match Group struct from /etc/group. If it cannot locate a group,
+// an ErrNoGroupEntries error is returned. When the provided group name
+// was an ID, a Group structure with Gid set is returned along with
+// ErrNoGroupEntries.
func GetGroup(containerMount, groupIDorName string) (*user.Group, error) {
var inputIsName bool
gid, err := strconv.Atoi(groupIDorName)
@@ -154,5 +161,8 @@ func GetGroup(containerMount, groupIDorName string) (*user.Group, error) {
if len(groups) > 0 {
return &groups[0], nil
}
+ if !inputIsName {
+ return &user.Group{Gid: gid}, user.ErrNoGroupEntries
+ }
return nil, user.ErrNoGroupEntries
}
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index bee833fa9..832efd554 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -223,7 +223,12 @@ func (n NetworkMode) IsBridge() bool {
return n == "bridge"
}
+// IsSlirp4netns indicates if we are running a rootless network stack
+func (n NetworkMode) IsSlirp4netns() bool {
+ return n == "slirp4netns"
+}
+
// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
- return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
+ return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() && !n.IsSlirp4netns()
}
diff --git a/pkg/registries/registries.go b/pkg/registries/registries.go
index 73aa93d68..cbb8b730c 100644
--- a/pkg/registries/registries.go
+++ b/pkg/registries/registries.go
@@ -13,21 +13,28 @@ import (
// userRegistriesFile is the path to the per user registry configuration file.
var userRegistriesFile = filepath.Join(os.Getenv("HOME"), ".config/containers/registries.conf")
-// GetRegistries obtains the list of registries defined in the global registries file.
-func GetRegistries() ([]string, error) {
- registryConfigPath := ""
+// SystemRegistriesConfPath returns an appropriate value for types.SystemContext.SystemRegistriesConfPath
+// (possibly "", which is not an error), taking into account rootless mode and environment variable overrides.
+//
+// FIXME: This should be centralized in a global SystemContext initializer inherited throughout the code,
+// not haphazardly called throughout the way it is being called now.
+func SystemRegistriesConfPath() string {
+ if envOverride := os.Getenv("REGISTRIES_CONFIG_PATH"); len(envOverride) > 0 {
+ return envOverride
+ }
if rootless.IsRootless() {
if _, err := os.Stat(userRegistriesFile); err == nil {
- registryConfigPath = userRegistriesFile
+ return userRegistriesFile
}
}
- envOverride := os.Getenv("REGISTRIES_CONFIG_PATH")
- if len(envOverride) > 0 {
- registryConfigPath = envOverride
- }
- searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath})
+ return ""
+}
+
+// GetRegistries obtains the list of registries defined in the global registries file.
+func GetRegistries() ([]string, error) {
+ searchRegistries, err := sysregistries.GetRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
if err != nil {
return nil, errors.Wrapf(err, "unable to parse the registries.conf file")
}
@@ -36,17 +43,7 @@ func GetRegistries() ([]string, error) {
// GetInsecureRegistries obtains the list of insecure registries from the global registration file.
func GetInsecureRegistries() ([]string, error) {
- registryConfigPath := ""
-
- if _, err := os.Stat(userRegistriesFile); err == nil {
- registryConfigPath = userRegistriesFile
- }
-
- envOverride := os.Getenv("REGISTRIES_CONFIG_PATH")
- if len(envOverride) > 0 {
- registryConfigPath = envOverride
- }
- registries, err := sysregistries.GetInsecureRegistries(&types.SystemContext{SystemRegistriesConfPath: registryConfigPath})
+ registries, err := sysregistries.GetInsecureRegistries(&types.SystemContext{SystemRegistriesConfPath: SystemRegistriesConfPath()})
if err != nil {
return nil, errors.Wrapf(err, "unable to parse the registries.conf file")
}
diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go
index fccd60093..e85bcb377 100644
--- a/pkg/resolvconf/resolvconf.go
+++ b/pkg/resolvconf/resolvconf.go
@@ -103,13 +103,21 @@ func GetLastModified() *File {
}
// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs:
-// 1. It looks for localhost (127.*|::1) entries in the provided
+// 1. If a netns is enabled, it looks for localhost (127.*|::1) entries in the provided
// resolv.conf, removing local nameserver entries, and, if the resulting
// cleaned config has no defined nameservers left, adds default DNS entries
// 2. Given the caller provides the enable/disable state of IPv6, the filter
// code will remove all IPv6 nameservers if it is not enabled for containers
//
-func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
+func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool, netnsEnabled bool) (*File, error) {
+ // If we're using the host netns, we have nothing to do besides hash the file.
+ if !netnsEnabled {
+ hash, err := ioutils.HashData(bytes.NewReader(resolvConf))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: resolvConf, Hash: hash}, nil
+ }
cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
// if IPv6 is not enabled, also clean out any IPv6 address nameserver
if !ipv6Enabled {
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 5c45f2694..07002da3f 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -12,6 +12,7 @@ import (
"runtime"
"strconv"
"strings"
+ "sync"
"syscall"
"unsafe"
@@ -33,9 +34,17 @@ func runInUser() error {
return nil
}
+var (
+ isRootlessOnce sync.Once
+ isRootless bool
+)
+
// IsRootless tells us if we are running in rootless mode
func IsRootless() bool {
- return os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != ""
+ isRootlessOnce.Do(func() {
+ isRootless = os.Geteuid() != 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != ""
+ })
+ return isRootless
}
var (
@@ -65,7 +74,7 @@ func GetRootlessUID() int {
func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error {
path, err := exec.LookPath(tool)
if err != nil {
- return err
+ return errors.Wrapf(err, "cannot find %s", tool)
}
appendTriplet := func(l []string, a, b, c int) []string {
@@ -83,7 +92,11 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap)
Path: path,
Args: args,
}
- return cmd.Run()
+
+ if err := cmd.Run(); err != nil {
+ return errors.Wrapf(err, "cannot setup namespace using %s", tool)
+ }
+ return nil
}
// JoinNS re-exec podman in a new userNS and join the user namespace of the specified
@@ -182,11 +195,16 @@ func BecomeRootInUserNS() (bool, int, error) {
return false, -1, errors.Errorf("cannot re-exec process")
}
+ allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != ""
+
var uids, gids []idtools.IDMap
username := os.Getenv("USER")
if username == "" {
user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid()))
- if err != nil && os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" {
+ if err != nil && !allowSingleIDMapping {
+ if os.IsNotExist(err) {
+ return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files")
+ }
return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set")
}
if err == nil {
@@ -194,7 +212,7 @@ func BecomeRootInUserNS() (bool, int, error) {
}
}
mappings, err := idtools.NewIDMappings(username, username)
- if os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") == "" {
+ if !allowSingleIDMapping {
if err != nil {
return false, -1, err
}
@@ -224,7 +242,11 @@ func BecomeRootInUserNS() (bool, int, error) {
uidsMapped := false
if mappings != nil && uids != nil {
- uidsMapped = tryMappingTool("newuidmap", pid, os.Getuid(), uids) == nil
+ err := tryMappingTool("newuidmap", pid, os.Getuid(), uids)
+ if !allowSingleIDMapping && err != nil {
+ return false, 0, err
+ }
+ uidsMapped = err == nil
}
if !uidsMapped {
setgroups := fmt.Sprintf("/proc/%d/setgroups", pid)
@@ -242,7 +264,11 @@ func BecomeRootInUserNS() (bool, int, error) {
gidsMapped := false
if mappings != nil && gids != nil {
- gidsMapped = tryMappingTool("newgidmap", pid, os.Getgid(), gids) == nil
+ err := tryMappingTool("newgidmap", pid, os.Getgid(), gids)
+ if !allowSingleIDMapping && err != nil {
+ return false, 0, err
+ }
+ gidsMapped = err == nil
}
if !gidsMapped {
gidMap := fmt.Sprintf("/proc/%d/gid_map", pid)
diff --git a/pkg/secrets/secrets.go b/pkg/secrets/secrets.go
index 7208f53b7..242953609 100644
--- a/pkg/secrets/secrets.go
+++ b/pkg/secrets/secrets.go
@@ -149,6 +149,15 @@ func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPre
mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...)
if rootless.IsRootless() {
mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...)
+ _, err := os.Stat(UserOverrideMountsFile)
+ if err != nil && os.IsNotExist(err) {
+ os.MkdirAll(filepath.Dir(UserOverrideMountsFile), 0755)
+ if f, err := os.Create(UserOverrideMountsFile); err != nil {
+ logrus.Warnf("could not create file %s: %v", UserOverrideMountsFile, err)
+ } else {
+ f.Close()
+ }
+ }
}
} else {
mountFiles = append(mountFiles, mountFile)
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 6ac9d82da..25f8cd7a1 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
}
options = append(options, runtime.WithPod(pod))
}
-
if len(c.PortBindings) > 0 {
portBindings, err = c.CreatePortBindings()
if err != nil {
@@ -392,11 +391,11 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
options = append(options, libpod.WithNetNSFrom(connectedCtr))
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
isRootless := rootless.IsRootless()
- postConfigureNetNS := isRootless || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
+ postConfigureNetNS := c.NetMode.IsSlirp4netns() || (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost()
if isRootless && len(portBindings) > 0 {
return nil, errors.New("port bindings are not yet supported by rootless containers")
}
- options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, networks))
+ options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
}
if c.PidMode.IsContainer() {
@@ -497,8 +496,13 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
func (c *CreateConfig) CreatePortBindings() ([]ocicni.PortMapping, error) {
+ return NatToOCIPortBindings(c.PortBindings)
+}
+
+// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice
+func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) {
var portBindings []ocicni.PortMapping
- for containerPb, hostPb := range c.PortBindings {
+ for containerPb, hostPb := range ports {
var pm ocicni.PortMapping
pm.ContainerPort = int32(containerPb.Int())
for _, i := range hostPb {
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index b1cca2c9e..05be00864 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -453,6 +453,9 @@ func addNetNS(config *CreateConfig, g *generate.Generator) error {
} else if IsPod(string(netMode)) {
logrus.Debug("Using pod netmode, unless pod is not sharing")
return nil
+ } else if netMode.IsSlirp4netns() {
+ logrus.Debug("Using slirp4netns netmode")
+ return nil
} else if netMode.IsUserDefined() {
logrus.Debug("Using user defined netmode")
return nil
diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go
new file mode 100644
index 000000000..efc760364
--- /dev/null
+++ b/pkg/trust/trust.go
@@ -0,0 +1,250 @@
+package trust
+
+import (
+ "bufio"
+ "encoding/base64"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "unsafe"
+
+ "github.com/containers/image/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ yaml "gopkg.in/yaml.v2"
+)
+
+// PolicyContent struct for policy.json file
+type PolicyContent struct {
+ Default []RepoContent `json:"default"`
+ Transports TransportsContent `json:"transports"`
+}
+
+// RepoContent struct used under each repo
+type RepoContent struct {
+ Type string `json:"type"`
+ KeyType string `json:"keyType,omitempty"`
+ KeyPath string `json:"keyPath,omitempty"`
+ KeyData string `json:"keyData,omitempty"`
+ SignedIdentity json.RawMessage `json:"signedIdentity,omitempty"`
+}
+
+// RepoMap map repo name to policycontent for each repo
+type RepoMap map[string][]RepoContent
+
+// TransportsContent struct for content under "transports"
+type TransportsContent map[string]RepoMap
+
+// RegistryConfiguration is one of the files in registriesDirPath configuring lookaside locations, or the result of merging them all.
+// NOTE: Keep this in sync with docs/registries.d.md!
+type RegistryConfiguration struct {
+ DefaultDocker *RegistryNamespace `json:"default-docker"`
+ // The key is a namespace, using fully-expanded Docker reference format or parent namespaces (per dockerReference.PolicyConfiguration*),
+ Docker map[string]RegistryNamespace `json:"docker"`
+}
+
+// RegistryNamespace defines lookaside locations for a single namespace.
+type RegistryNamespace struct {
+ SigStore string `json:"sigstore"` // For reading, and if SigStoreStaging is not present, for writing.
+ SigStoreStaging string `json:"sigstore-staging"` // For writing only.
+}
+
+// DefaultPolicyPath returns a path to the default policy of the system.
+func DefaultPolicyPath(sys *types.SystemContext) string {
+ systemDefaultPolicyPath := "/etc/containers/policy.json"
+ if sys != nil {
+ if sys.SignaturePolicyPath != "" {
+ return sys.SignaturePolicyPath
+ }
+ if sys.RootForImplicitAbsolutePaths != "" {
+ return filepath.Join(sys.RootForImplicitAbsolutePaths, systemDefaultPolicyPath)
+ }
+ }
+ return systemDefaultPolicyPath
+}
+
+// RegistriesDirPath returns a path to registries.d
+func RegistriesDirPath(sys *types.SystemContext) string {
+ systemRegistriesDirPath := "/etc/containers/registries.d"
+ if sys != nil {
+ if sys.RegistriesDirPath != "" {
+ return sys.RegistriesDirPath
+ }
+ if sys.RootForImplicitAbsolutePaths != "" {
+ return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
+ }
+ }
+ return systemRegistriesDirPath
+}
+
+// LoadAndMergeConfig loads configuration files in dirPath
+func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) {
+ mergedConfig := RegistryConfiguration{Docker: map[string]RegistryNamespace{}}
+ dockerDefaultMergedFrom := ""
+ nsMergedFrom := map[string]string{}
+
+ dir, err := os.Open(dirPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return &mergedConfig, nil
+ }
+ return nil, err
+ }
+ configNames, err := dir.Readdirnames(0)
+ if err != nil {
+ return nil, err
+ }
+ for _, configName := range configNames {
+ if !strings.HasSuffix(configName, ".yaml") {
+ continue
+ }
+ configPath := filepath.Join(dirPath, configName)
+ configBytes, err := ioutil.ReadFile(configPath)
+ if err != nil {
+ return nil, err
+ }
+ var config RegistryConfiguration
+ err = yaml.Unmarshal(configBytes, &config)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error parsing %s", configPath)
+ }
+ if config.DefaultDocker != nil {
+ if mergedConfig.DefaultDocker != nil {
+ return nil, errors.Errorf(`Error parsing signature storage configuration: "default-docker" defined both in "%s" and "%s"`,
+ dockerDefaultMergedFrom, configPath)
+ }
+ mergedConfig.DefaultDocker = config.DefaultDocker
+ dockerDefaultMergedFrom = configPath
+ }
+ for nsName, nsConfig := range config.Docker { // includes config.Docker == nil
+ if _, ok := mergedConfig.Docker[nsName]; ok {
+ return nil, errors.Errorf(`Error parsing signature storage configuration: "docker" namespace "%s" defined both in "%s" and "%s"`,
+ nsName, nsMergedFrom[nsName], configPath)
+ }
+ mergedConfig.Docker[nsName] = nsConfig
+ nsMergedFrom[nsName] = configPath
+ }
+ }
+ return &mergedConfig, nil
+}
+
+// HaveMatchRegistry checks if trust settings for the registry have been configed in yaml file
+func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *RegistryNamespace {
+ searchKey := key
+ if !strings.Contains(searchKey, "/") {
+ val, exists := registryConfigs.Docker[searchKey]
+ if exists {
+ return &val
+ }
+ }
+ for range strings.Split(key, "/") {
+ val, exists := registryConfigs.Docker[searchKey]
+ if exists {
+ return &val
+ }
+ if strings.Contains(searchKey, "/") {
+ searchKey = searchKey[:strings.LastIndex(searchKey, "/")]
+ }
+ }
+ return nil
+}
+
+// CreateTmpFile creates a temp file under dir and writes the content into it
+func CreateTmpFile(dir, pattern string, content []byte) (string, error) {
+ tmpfile, err := ioutil.TempFile(dir, pattern)
+ if err != nil {
+ return "", err
+ }
+ defer tmpfile.Close()
+
+ if _, err := tmpfile.Write(content); err != nil {
+ return "", err
+
+ }
+ return tmpfile.Name(), nil
+}
+
+// GetGPGId return GPG identity, either bracketed <email> or ID string
+// comma separated if more than one key
+func GetGPGId(keys []string) string {
+ for _, k := range keys {
+ if _, err := os.Stat(k); err != nil {
+ decodeKey, err := base64.StdEncoding.DecodeString(k)
+ if err != nil {
+ logrus.Warnf("error decoding key data")
+ continue
+ }
+ tmpfileName, err := CreateTmpFile("/run/", "", decodeKey)
+ if err != nil {
+ logrus.Warnf("error creating key date temp file %s", err)
+ }
+ defer os.Remove(tmpfileName)
+ k = tmpfileName
+ }
+ cmd := exec.Command("gpg2", "--with-colons", k)
+ results, err := cmd.Output()
+ if err != nil {
+ logrus.Warnf("error get key identity: %s", err)
+ continue
+ }
+ resultsStr := *(*string)(unsafe.Pointer(&results))
+ scanner := bufio.NewScanner(strings.NewReader(resultsStr))
+ var parseduids []string
+ for scanner.Scan() {
+ line := scanner.Text()
+ if strings.HasPrefix(line, "uid:") || strings.HasPrefix(line, "pub:") {
+ uid := strings.Split(line, ":")[9]
+ if uid == "" {
+ continue
+ }
+ parseduid := uid
+ if strings.Contains(uid, "<") && strings.Contains(uid, ">") {
+ parseduid = strings.SplitN(strings.SplitAfterN(uid, "<", 2)[1], ">", 2)[0]
+ }
+ parseduids = append(parseduids, parseduid)
+ }
+ }
+ return strings.Join(parseduids, ",")
+ }
+ return ""
+}
+
+// GetPolicyJSON return the struct to show policy.json in json format
+func GetPolicyJSON(policyContentStruct PolicyContent, systemRegistriesDirPath string) (map[string]map[string]interface{}, error) {
+ registryConfigs, err := LoadAndMergeConfig(systemRegistriesDirPath)
+ if err != nil {
+ return nil, err
+ }
+
+ policyJSON := make(map[string]map[string]interface{})
+ if len(policyContentStruct.Default) > 0 {
+ policyJSON["* (default)"] = make(map[string]interface{})
+ policyJSON["* (default)"]["type"] = policyContentStruct.Default[0].Type
+ }
+ for transname, transval := range policyContentStruct.Transports {
+ for repo, repoval := range transval {
+ policyJSON[repo] = make(map[string]interface{})
+ policyJSON[repo]["type"] = repoval[0].Type
+ policyJSON[repo]["transport"] = transname
+ for _, repoele := range repoval {
+ keyarr := []string{}
+ if len(repoele.KeyPath) > 0 {
+ keyarr = append(keyarr, repoele.KeyPath)
+ }
+ if len(repoele.KeyData) > 0 {
+ keyarr = append(keyarr, string(repoele.KeyData))
+ }
+ policyJSON[repo]["keys"] = keyarr
+ }
+ policyJSON[repo]["sigstore"] = ""
+ registryNamespace := HaveMatchRegistry(repo, registryConfigs)
+ if registryNamespace != nil {
+ policyJSON[repo]["sigstore"] = registryNamespace.SigStore
+ }
+ }
+ }
+ return policyJSON, nil
+}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 69f49e72a..f567f2675 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -3,11 +3,13 @@ package util
import (
"fmt"
"os"
+ "os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
+ "github.com/BurntSushi/toml"
"github.com/containers/image/types"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
@@ -248,49 +250,122 @@ func GetRootlessRuntimeDir() (string, error) {
return runtimeDir, nil
}
-// GetRootlessStorageOpts returns the storage ops for containers running as non root
-func GetRootlessStorageOpts() (storage.StoreOptions, error) {
- var opts storage.StoreOptions
-
+// GetRootlessDirInfo returns the parent path of where the storage for containers and
+// volumes will be in rootless mode
+func GetRootlessDirInfo() (string, string, error) {
rootlessRuntime, err := GetRootlessRuntimeDir()
if err != nil {
- return opts, err
+ return "", "", err
}
- opts.RunRoot = rootlessRuntime
dataDir := os.Getenv("XDG_DATA_HOME")
if dataDir == "" {
home := os.Getenv("HOME")
if home == "" {
- return opts, fmt.Errorf("neither XDG_DATA_HOME nor HOME was set non-empty")
+ return "", "", fmt.Errorf("neither XDG_DATA_HOME nor HOME was set non-empty")
}
// runc doesn't like symlinks in the rootfs path, and at least
// on CoreOS /home is a symlink to /var/home, so resolve any symlink.
resolvedHome, err := filepath.EvalSymlinks(home)
if err != nil {
- return opts, errors.Wrapf(err, "cannot resolve %s", home)
+ return "", "", errors.Wrapf(err, "cannot resolve %s", home)
}
dataDir = filepath.Join(resolvedHome, ".local", "share")
}
+ return dataDir, rootlessRuntime, nil
+}
+
+// GetRootlessStorageOpts returns the storage opts for containers running as non root
+func GetRootlessStorageOpts() (storage.StoreOptions, error) {
+ var opts storage.StoreOptions
+
+ dataDir, rootlessRuntime, err := GetRootlessDirInfo()
+ if err != nil {
+ return opts, err
+ }
+ opts.RunRoot = rootlessRuntime
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
- opts.GraphDriverName = "vfs"
+ if path, err := exec.LookPath("fuse-overlayfs"); err == nil {
+ opts.GraphDriverName = "overlay"
+ opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)}
+ } else {
+ opts.GraphDriverName = "vfs"
+ }
return opts, nil
}
-// GetDefaultStoreOptions returns the storage ops for containers
-func GetDefaultStoreOptions() (storage.StoreOptions, error) {
+// GetRootlessVolumeInfo returns where all the name volumes will be created in rootless mode
+func GetRootlessVolumeInfo() (string, error) {
+ dataDir, _, err := GetRootlessDirInfo()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(dataDir, "containers", "storage", "volumes"), nil
+}
+
+type tomlOptionsConfig struct {
+ MountProgram string `toml:"mount_program"`
+}
+
+type tomlConfig struct {
+ Storage struct {
+ Driver string `toml:"driver"`
+ RunRoot string `toml:"runroot"`
+ GraphRoot string `toml:"graphroot"`
+ Options struct{ tomlOptionsConfig } `toml:"options"`
+ } `toml:"storage"`
+}
+
+func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig {
+ config := new(tomlConfig)
+
+ config.Storage.Driver = storeOptions.GraphDriverName
+ config.Storage.RunRoot = storeOptions.RunRoot
+ config.Storage.GraphRoot = storeOptions.GraphRoot
+ for _, i := range storeOptions.GraphDriverOptions {
+ s := strings.Split(i, "=")
+ if s[0] == "overlay.mount_program" {
+ config.Storage.Options.MountProgram = s[1]
+ }
+ }
+
+ return config
+}
+
+// GetDefaultStoreOptions returns the storage ops for containers and the volume path
+// for the volume API
+// It also returns the path where all named volumes will be created using the volume API
+func GetDefaultStoreOptions() (storage.StoreOptions, string, error) {
storageOpts := storage.DefaultStoreOptions
+ volumePath := "/var/lib/containers/storage"
if rootless.IsRootless() {
var err error
storageOpts, err = GetRootlessStorageOpts()
if err != nil {
- return storageOpts, err
+ return storageOpts, volumePath, err
+ }
+ volumePath, err = GetRootlessVolumeInfo()
+ if err != nil {
+ return storageOpts, volumePath, err
}
storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf")
if _, err := os.Stat(storageConf); err == nil {
storage.ReloadConfigurationFile(storageConf, &storageOpts)
+ } else if os.IsNotExist(err) {
+ os.MkdirAll(filepath.Dir(storageConf), 0755)
+ file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
+ if err != nil {
+ return storageOpts, volumePath, errors.Wrapf(err, "cannot open %s", storageConf)
+ }
+
+ tomlConfiguration := getTomlStorage(&storageOpts)
+ defer file.Close()
+ enc := toml.NewEncoder(file)
+ if err := enc.Encode(tomlConfiguration); err != nil {
+ os.Remove(storageConf)
+ }
}
}
- return storageOpts, nil
+ return storageOpts, volumePath, nil
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index f517e9b6e..07d981786 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -278,6 +278,18 @@ func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, tim
return call.ReplyRestartContainer(ctr.ID())
}
+// ContainerExists looks in local storage for the existence of a container
+func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error {
+ _, err := i.Runtime.LookupContainer(name)
+ if errors.Cause(err) == libpod.ErrNoSuchCtr {
+ return call.ReplyContainerExists(1)
+ }
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyContainerExists(0)
+}
+
// KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1
// for the signal arg.
func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error {
@@ -413,3 +425,40 @@ func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) err
}
return call.ReplyGetAttachSockets(s)
}
+
+// ContainerCheckpoint ...
+func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error {
+ ctx := getContext()
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyContainerNotFound(name)
+ }
+
+ options := libpod.ContainerCheckpointOptions{
+ Keep: keep,
+ TCPEstablished: tcpEstablished,
+ KeepRunning: leaveRunning,
+ }
+ if err := ctr.Checkpoint(ctx, options); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyContainerCheckpoint(ctr.ID())
+}
+
+// ContainerRestore ...
+func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error {
+ ctx := getContext()
+ ctr, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyContainerNotFound(name)
+ }
+
+ options := libpod.ContainerCheckpointOptions{
+ Keep: keep,
+ TCPEstablished: tcpEstablished,
+ }
+ if err := ctr.Restore(ctx, options); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyContainerRestore(ctr.ID())
+}
diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go
index ca1a57048..bb6273fd1 100644
--- a/pkg/varlinkapi/containers_create.go
+++ b/pkg/varlinkapi/containers_create.go
@@ -13,6 +13,7 @@ import (
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/rootless"
cc "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/util"
"github.com/docker/docker/pkg/signal"
@@ -24,7 +25,7 @@ func (i *LibpodAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.C
rtc := i.Runtime.GetConfig()
ctx := getContext()
- newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false, false)
+ newImage, err := i.Runtime.ImageRuntime().New(ctx, config.Image, rtc.SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{}, false)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
@@ -126,7 +127,11 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru
// NETWORK MODE
networkMode := create.Net_mode
if networkMode == "" {
- networkMode = "bridge"
+ if rootless.IsRootless() {
+ networkMode = "slirp4netns"
+ } else {
+ networkMode = "bridge"
+ }
}
// WORKING DIR
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index d14c61c39..5e4cb4ccb 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "os"
"path/filepath"
"strings"
"time"
@@ -12,13 +13,17 @@ import (
"github.com/containers/buildah"
"github.com/containers/buildah/imagebuildah"
"github.com/containers/image/docker"
+ dockerarchive "github.com/containers/image/docker/archive"
"github.com/containers/image/manifest"
+ "github.com/containers/image/transports/alltransports"
"github.com/containers/image/types"
+ "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
sysreg "github.com/containers/libpod/pkg/registries"
"github.com/containers/libpod/pkg/util"
+ "github.com/containers/libpod/utils"
"github.com/docker/go-units"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -127,7 +132,7 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI
if config.Pull_always {
pullPolicy = imagebuildah.PullAlways
}
- manifestType := "oci"
+ manifestType := "oci" //nolint
if config.Image_format != "" {
manifestType = config.Image_format
}
@@ -271,6 +276,9 @@ func (i *LibpodAPI) InspectImage(call iopodman.VarlinkCall, name string) error {
return call.ReplyImageNotFound(name)
}
inspectInfo, err := newImage.Inspect(getContext())
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
b, err := json.Marshal(inspectInfo)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize"))
@@ -305,8 +313,12 @@ func (i *LibpodAPI) HistoryImage(call iopodman.VarlinkCall, name string) error {
}
// PushImage pushes an local image to registry
-// TODO We need to add options for signing, credentials, tls, and multi-tag
-func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, tlsVerify bool) error {
+func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, tlsVerify bool, signaturePolicy, creds, certDir string, compress bool, format string, removeSignatures bool, signBy string) error {
+ var (
+ registryCreds *types.DockerAuthConfig
+ manifestType string
+ )
+
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(err.Error())
@@ -315,14 +327,38 @@ func (i *LibpodAPI) PushImage(call iopodman.VarlinkCall, name, tag string, tlsVe
if tag != "" {
destname = tag
}
-
+ if creds != "" {
+ creds, err := util.ParseRegistryCreds(creds)
+ if err != nil {
+ return err
+ }
+ registryCreds = creds
+ }
dockerRegistryOptions := image.DockerRegistryOptions{
- DockerInsecureSkipTLSVerify: !tlsVerify,
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: certDir,
+ }
+ if !tlsVerify {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue
+ }
+ if format != "" {
+ switch format {
+ case "oci": //nolint
+ manifestType = v1.MediaTypeImageManifest
+ case "v2s1":
+ manifestType = manifest.DockerV2Schema1SignedMediaType
+ case "v2s2", "docker":
+ manifestType = manifest.DockerV2Schema2MediaType
+ default:
+ return call.ReplyErrorOccurred(fmt.Sprintf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", format))
+ }
+ }
+ so := image.SigningOptions{
+ RemoveSignatures: removeSignatures,
+ SignBy: signBy,
}
- so := image.SigningOptions{}
-
- if err := newImage.PushImageToHeuristicDestination(getContext(), destname, "", "", "", nil, false, so, &dockerRegistryOptions, false, nil); err != nil {
+ if err := newImage.PushImageToHeuristicDestination(getContext(), destname, manifestType, "", signaturePolicy, nil, compress, so, &dockerRegistryOptions, nil); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyPushImage(newImage.ID())
@@ -421,7 +457,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch
sc := image.GetSystemContext(i.Runtime.GetConfig().SignaturePolicyPath, "", false)
var mimeType string
switch manifestType {
- case "oci", "":
+ case "oci", "": //nolint
mimeType = buildah.OCIv1ImageManifest
case "docker":
mimeType = manifest.DockerV2Schema2MediaType
@@ -482,18 +518,96 @@ func (i *LibpodAPI) ExportImage(call iopodman.VarlinkCall, name, destination str
return err
}
- if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, false, additionalTags); err != nil {
+ if err := newImage.PushImageToHeuristicDestination(getContext(), destination, "", "", "", nil, compress, image.SigningOptions{}, &image.DockerRegistryOptions{}, additionalTags); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportImage(newImage.ID())
}
// PullImage pulls an image from a registry to the image store.
-// TODO This implementation is incomplete
-func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string) error {
- newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, "", "", nil, &image.DockerRegistryOptions{}, image.SigningOptions{}, true, false)
+func (i *LibpodAPI) PullImage(call iopodman.VarlinkCall, name string, certDir, creds, signaturePolicy string, tlsVerify bool) error {
+ var (
+ registryCreds *types.DockerAuthConfig
+ imageID string
+ )
+ if creds != "" {
+ creds, err := util.ParseRegistryCreds(creds)
+ if err != nil {
+ return err
+ }
+ registryCreds = creds
+ }
+
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: certDir,
+ }
+ if tlsVerify {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!tlsVerify)
+ }
+
+ so := image.SigningOptions{}
+
+ if strings.HasPrefix(name, dockerarchive.Transport.Name()+":") {
+ srcRef, err := alltransports.ParseImageName(name)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing %q", name)
+ }
+ newImage, err := i.Runtime.ImageRuntime().LoadFromArchiveReference(getContext(), srcRef, signaturePolicy, nil)
+ if err != nil {
+ return errors.Wrapf(err, "error pulling image from %q", name)
+ }
+ imageID = newImage[0].ID()
+ } else {
+ newImage, err := i.Runtime.ImageRuntime().New(getContext(), name, signaturePolicy, "", nil, &dockerRegistryOptions, so, false)
+ if err != nil {
+ return call.ReplyErrorOccurred(fmt.Sprintf("unable to pull %s: %s", name, err.Error()))
+ }
+ imageID = newImage.ID()
+ }
+ return call.ReplyPullImage(imageID)
+}
+
+// ImageExists returns bool as to whether the input image exists in local storage
+func (i *LibpodAPI) ImageExists(call iopodman.VarlinkCall, name string) error {
+ _, err := i.Runtime.ImageRuntime().NewFromLocal(name)
+ if errors.Cause(err) == image.ErrNoSuchImage {
+ return call.ReplyImageExists(1)
+ }
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyImageExists(0)
+}
+
+// ContainerRunlabel ...
+func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.Runlabel) error {
+ ctx := getContext()
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerCertPath: input.CertDir,
+ }
+ if !input.TlsVerify {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.OptionalBoolTrue
+ }
+
+ stdErr := os.Stderr
+ stdOut := os.Stdout
+ stdIn := os.Stdin
+
+ runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, input.Creds, dockerRegistryOptions, input.Authfile, input.SignaturePolicyPath, nil)
+ if err != nil {
+ return err
+ }
+ if runLabel == "" {
+ return nil
+ }
+
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs)
if err != nil {
- return call.ReplyErrorOccurred(fmt.Sprintf("unable to pull %s: %s", name, err.Error()))
+ return err
+ }
+ if err := utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
}
- return call.ReplyPullImage(newImage.ID())
+ return call.ReplyContainerRunlabel()
}
diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go
new file mode 100644
index 000000000..84e6b2709
--- /dev/null
+++ b/pkg/varlinkapi/mount.go
@@ -0,0 +1,49 @@
+package varlinkapi
+
+import (
+ "github.com/containers/libpod/cmd/podman/varlink"
+)
+
+// ListContainerMounts ...
+func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error {
+ var mounts []string
+ allContainers, err := i.Runtime.GetAllContainers()
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ for _, container := range allContainers {
+ mounted, mountPoint, err := container.Mounted()
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ if mounted {
+ mounts = append(mounts, mountPoint)
+ }
+ }
+ return call.ReplyListContainerMounts(mounts)
+}
+
+// MountContainer ...
+func (i *LibpodAPI) MountContainer(call iopodman.VarlinkCall, name string) error {
+ container, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ path, err := container.Mount()
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyMountContainer(path)
+}
+
+// UnmountContainer ...
+func (i *LibpodAPI) UnmountContainer(call iopodman.VarlinkCall, name string, force bool) error {
+ container, err := i.Runtime.LookupContainer(name)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ if err := container.Unmount(force); err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyUnmountContainer()
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 7930a956f..6e758786a 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -2,6 +2,7 @@ package varlinkapi
import (
"encoding/json"
+ "github.com/containers/libpod/pkg/rootless"
"syscall"
"github.com/containers/libpod/cmd/podman/shared"
@@ -12,6 +13,10 @@ import (
// CreatePod ...
func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error {
var options []libpod.PodCreateOption
+
+ if create.InfraCommand != "" || create.InfraImage != "" {
+ return call.ReplyErrorOccurred("the infra-command and infra-image options are not supported yet")
+ }
if create.CgroupParent != "" {
options = append(options, libpod.WithPodCgroupParent(create.CgroupParent))
}
@@ -27,6 +32,21 @@ func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCrea
if len(create.Share) == 0 && create.Infra {
return call.ReplyErrorOccurred("You must share kernel namespaces to run an infra container")
}
+
+ if len(create.Publish) > 0 {
+ if !create.Infra {
+ return call.ReplyErrorOccurred("you must have an infra container to publish port bindings to the host")
+ }
+ if rootless.IsRootless() {
+ return call.ReplyErrorOccurred("rootless networking does not allow port binding to the host")
+ }
+ portBindings, err := shared.CreatePortBindings(create.Publish)
+ if err != nil {
+ return err
+ }
+ options = append(options, libpod.WithInfraContainerPorts(portBindings))
+
+ }
if create.Infra {
options = append(options, libpod.WithInfraContainer())
nsOptions, err := shared.GetNamespaceOptions(create.Share)
@@ -120,12 +140,12 @@ func (i *LibpodAPI) StartPod(call iopodman.VarlinkCall, name string) error {
}
// StopPod ...
-func (i *LibpodAPI) StopPod(call iopodman.VarlinkCall, name string) error {
+func (i *LibpodAPI) StopPod(call iopodman.VarlinkCall, name string, timeout int64) error {
pod, err := i.Runtime.LookupPod(name)
if err != nil {
return call.ReplyPodNotFound(name)
}
- ctrErrs, err := pod.Stop(getContext(), true)
+ ctrErrs, err := pod.StopWithTimeout(getContext(), true, int(timeout))
callErr := handlePodCall(call, pod, ctrErrs, err)
if callErr != nil {
return err