aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers.go11
-rw-r--r--pkg/api/handlers/compat/events.go2
-rw-r--r--pkg/api/handlers/compat/images.go4
-rw-r--r--pkg/api/handlers/compat/images_build.go4
-rw-r--r--pkg/api/handlers/compat/images_push.go4
-rw-r--r--pkg/api/handlers/compat/images_search.go4
-rw-r--r--pkg/api/handlers/libpod/images.go4
-rw-r--r--pkg/api/handlers/libpod/images_pull.go4
-rw-r--r--pkg/api/handlers/libpod/manifests.go4
-rw-r--r--pkg/api/handlers/libpod/play.go4
-rw-r--r--pkg/api/handlers/libpod/pods.go1
-rw-r--r--pkg/api/server/docs.go4
-rw-r--r--pkg/api/server/register_networks.go4
-rw-r--r--pkg/api/server/register_volumes.go4
-rw-r--r--pkg/auth/auth.go228
-rw-r--r--pkg/auth/auth_test.go377
-rw-r--r--pkg/bindings/images/build.go10
-rw-r--r--pkg/bindings/images/images.go5
-rw-r--r--pkg/bindings/images/pull.go3
-rw-r--r--pkg/bindings/play/play.go3
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/images.go36
-rw-r--r--pkg/domain/entities/pods.go4
-rw-r--r--pkg/domain/infra/abi/images.go179
-rw-r--r--pkg/domain/infra/tunnel/images.go2
-rw-r--r--pkg/machine/ignition.go53
-rw-r--r--pkg/rootless/rootless_linux.c2
-rw-r--r--pkg/specgen/generate/container_create.go111
-rw-r--r--pkg/specgen/generate/oci.go37
-rw-r--r--pkg/specgen/podspecgen.go5
-rw-r--r--pkg/util/utils.go4
31 files changed, 830 insertions, 289 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 5a06722ec..ad341c3ab 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -356,6 +356,15 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error
return nil, err
}
+ m, err := json.Marshal(inspect.Mounts)
+ if err != nil {
+ return nil, err
+ }
+ mounts := []types.MountPoint{}
+ if err := json.Unmarshal(m, &mounts); err != nil {
+ return nil, err
+ }
+
return &handlers.Container{Container: types.Container{
ID: l.ID(),
Names: []string{fmt.Sprintf("/%s", l.Name())},
@@ -374,7 +383,7 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error
}{
"host"},
NetworkSettings: &networkSettings,
- Mounts: nil,
+ Mounts: mounts,
},
ContainerCreateConfig: types.ContainerCreateConfig{},
}, nil
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 901acdac4..bc31a36c4 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -91,6 +91,8 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
e := entities.ConvertToEntitiesEvent(*evt)
if !utils.IsLibpodRequest(r) && e.Status == "died" {
e.Status = "die"
+ e.Action = "die"
+ e.Actor.Attributes["exitCode"] = e.Actor.Attributes["containerExitCode"]
}
if err := coder.Encode(e); err != nil {
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 4533fddeb..c1cc99da4 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -270,9 +270,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) {
return
}
- authConf, authfile, key, err := auth.GetCredentials(r)
+ authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 45e4543a9..0fcac5330 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -453,10 +453,10 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
}
- creds, authfile, key, err := auth.GetCredentials(r)
+ creds, authfile, err := auth.GetCredentials(r)
if err != nil {
// Credential value(s) not returned as their value is not human readable
- utils.BadRequest(w, key.String(), "n/a", err)
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index 3a84b5799..04cad204d 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -85,9 +85,9 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- authconf, authfile, key, err := auth.GetCredentials(r)
+ authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go
index e9cc3e2b6..f6ad86a04 100644
--- a/pkg/api/handlers/compat/images_search.go
+++ b/pkg/api/handlers/compat/images_search.go
@@ -34,9 +34,9 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
return
}
- _, authfile, key, err := auth.GetCredentials(r)
+ _, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index f2f93434a..6e23845f0 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -497,9 +497,9 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
return
}
- authconf, authfile, key, err := auth.GetCredentials(r)
+ authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go
index fabdb326b..518e7cc65 100644
--- a/pkg/api/handlers/libpod/images_pull.go
+++ b/pkg/api/handlers/libpod/images_pull.go
@@ -68,9 +68,9 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
}
// Do the auth dance.
- authConf, authfile, key, err := auth.GetCredentials(r)
+ authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index 869c83fa3..eb0b6827f 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -176,9 +176,9 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) {
}
source := utils.GetName(r)
- authconf, authfile, key, err := auth.GetCredentials(r)
+ authconf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
index 312aa32de..6ef83ad92 100644
--- a/pkg/api/handlers/libpod/play.go
+++ b/pkg/api/handlers/libpod/play.go
@@ -86,9 +86,9 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
return
}
- authConf, authfile, key, err := auth.GetCredentials(r)
+ authConf, authfile, err := auth.GetCredentials(r)
if err != nil {
- utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String()))
+ utils.Error(w, "failed to retrieve repository credentials", http.StatusBadRequest, err)
return
}
defer auth.RemoveAuthfile(authfile)
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 3d18406a5..1b29831b4 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -42,6 +42,7 @@ func PodCreate(w http.ResponseWriter, r *http.Request) {
infraOptions := entities.NewInfraContainerCreateOptions() // options for pulling the image and FillOutSpec
infraOptions.Net = &entities.NetOptions{}
infraOptions.Devices = psg.Devices
+ infraOptions.SecurityOpt = psg.SecurityOpt
err = specgenutil.FillOutSpecGen(psg.InfraContainerSpec, &infraOptions, []string{}) // necessary for default values in many cases (userns, idmappings)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error filling out specgen"))
diff --git a/pkg/api/server/docs.go b/pkg/api/server/docs.go
index 83d9ef160..2127e7d82 100644
--- a/pkg/api/server/docs.go
+++ b/pkg/api/server/docs.go
@@ -1,4 +1,4 @@
-// Package api Provides an API for the Libpod library
+// Package api Provides an API for the Libpod library
//
// This documentation describes the Podman v2.0 RESTful API.
// It replaces the Podman v1.0 API and was initially delivered
@@ -45,7 +45,7 @@
// Schemes: http, https
// Host: podman.io
// BasePath: /
-// Version: 3.2.0
+// Version: 4.0.0
// License: Apache-2.0 https://opensource.org/licenses/Apache-2.0
// Contact: Podman <podman@lists.podman.io> https://podman.io/community/
//
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index 344486299..77e8a80fd 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -101,7 +101,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// parameters:
// - in: body
// name: create
- // description: attributes for creating a container
+ // description: attributes for creating a network
// schema:
// $ref: "#/definitions/NetworkCreateRequest"
// responses:
@@ -312,7 +312,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// parameters:
// - in: body
// name: create
- // description: attributes for creating a container
+ // description: attributes for creating a network
// schema:
// $ref: "#/definitions/NetworkCreateLibpod"
// responses:
diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go
index fb02cffcf..d1c1d5024 100644
--- a/pkg/api/server/register_volumes.go
+++ b/pkg/api/server/register_volumes.go
@@ -17,7 +17,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// parameters:
// - in: body
// name: create
- // description: attributes for creating a container
+ // description: attributes for creating a volume
// schema:
// $ref: "#/definitions/VolumeCreate"
// produces:
@@ -188,7 +188,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error {
// - in: body
// name: create
// description: |
- // attributes for creating a container.
+ // attributes for creating a volume.
// Note: If a volume by the same name exists, a 201 response with that volume's information will be generated.
// schema:
// $ref: "#/definitions/DockerVolumeCreate"
diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go
index 070e222ad..f423c011d 100644
--- a/pkg/auth/auth.go
+++ b/pkg/auth/auth.go
@@ -3,7 +3,6 @@ package auth
import (
"encoding/base64"
"encoding/json"
- "fmt"
"io/ioutil"
"net/http"
"os"
@@ -16,52 +15,70 @@ import (
"github.com/sirupsen/logrus"
)
-type HeaderAuthName string
-
-func (h HeaderAuthName) String() string { return string(h) }
-
-// XRegistryAuthHeader is the key to the encoded registry authentication configuration in an http-request header.
-// This header supports one registry per header occurrence. To support N registries provided N headers, one per registry.
+// xRegistryAuthHeader is the key to the encoded registry authentication configuration in an http-request header.
+// This header supports one registry per header occurrence. To support N registries provide N headers, one per registry.
// As of Docker API 1.40 and Libpod API 1.0.0, this header is supported by all endpoints.
-const XRegistryAuthHeader HeaderAuthName = "X-Registry-Auth"
+const xRegistryAuthHeader = "X-Registry-Auth"
-// XRegistryConfigHeader is the key to the encoded registry authentication configuration in an http-request header.
+// xRegistryConfigHeader is the key to the encoded registry authentication configuration in an http-request header.
// This header supports N registries in one header via a Base64 encoded, JSON map.
// As of Docker API 1.40 and Libpod API 2.0.0, this header is supported by build endpoints.
-const XRegistryConfigHeader HeaderAuthName = "X-Registry-Config"
+const xRegistryConfigHeader = "X-Registry-Config"
// GetCredentials queries the http.Request for X-Registry-.* headers and extracts
-// the necessary authentication information for libpod operations
-func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, HeaderAuthName, error) {
- has := func(key HeaderAuthName) bool { hdr, found := r.Header[string(key)]; return found && len(hdr) > 0 }
- switch {
- case has(XRegistryConfigHeader):
- c, f, err := getConfigCredentials(r)
- return c, f, XRegistryConfigHeader, err
- case has(XRegistryAuthHeader):
- c, f, err := getAuthCredentials(r)
- return c, f, XRegistryAuthHeader, err
- }
- return nil, "", "", nil
+// the necessary authentication information for libpod operations, possibly
+// creating a config file. If that is the case, the caller must call RemoveAuthFile.
+func GetCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
+ nonemptyHeaderValue := func(key string) ([]string, bool) {
+ hdr := r.Header.Values(key)
+ return hdr, len(hdr) > 0
+ }
+ var override *types.DockerAuthConfig
+ var fileContents map[string]types.DockerAuthConfig
+ var headerName string
+ var err error
+ if hdr, ok := nonemptyHeaderValue(xRegistryConfigHeader); ok {
+ headerName = xRegistryConfigHeader
+ override, fileContents, err = getConfigCredentials(r, hdr)
+ } else if hdr, ok := nonemptyHeaderValue(xRegistryAuthHeader); ok {
+ headerName = xRegistryAuthHeader
+ override, fileContents, err = getAuthCredentials(hdr)
+ } else {
+ return nil, "", nil
+ }
+ if err != nil {
+ return nil, "", errors.Wrapf(err, "failed to parse %q header for %s", headerName, r.URL.String())
+ }
+
+ var authFile string
+ if fileContents == nil {
+ authFile = ""
+ } else {
+ authFile, err = authConfigsToAuthFile(fileContents)
+ if err != nil {
+ return nil, "", errors.Wrapf(err, "failed to parse %q header for %s", headerName, r.URL.String())
+ }
+ }
+ return override, authFile, nil
}
-// getConfigCredentials extracts one or more docker.AuthConfig from the request's
-// header. An empty key will be used as default while a named registry will be
+// getConfigCredentials extracts one or more docker.AuthConfig from a request and its
+// xRegistryConfigHeader value. An empty key will be used as default while a named registry will be
// returned as types.DockerAuthConfig
-func getConfigCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
+func getConfigCredentials(r *http.Request, headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) {
var auth *types.DockerAuthConfig
configs := make(map[string]types.DockerAuthConfig)
- for _, h := range r.Header[string(XRegistryConfigHeader)] {
+ for _, h := range headers {
param, err := base64.URLEncoding.DecodeString(h)
if err != nil {
- return nil, "", errors.Wrapf(err, "failed to decode %q", XRegistryConfigHeader)
+ return nil, nil, errors.Wrapf(err, "failed to decode %q", xRegistryConfigHeader)
}
ac := make(map[string]dockerAPITypes.AuthConfig)
err = json.Unmarshal(param, &ac)
if err != nil {
- return nil, "", errors.Wrapf(err, "failed to unmarshal %q", XRegistryConfigHeader)
+ return nil, nil, errors.Wrapf(err, "failed to unmarshal %q", xRegistryConfigHeader)
}
for k, v := range ac {
@@ -91,79 +108,45 @@ func getConfigCredentials(r *http.Request) (*types.DockerAuthConfig, string, err
if auth == nil {
logrus.Debugf("%q header found in request, but \"registry=%v\" query parameter not provided",
- XRegistryConfigHeader, registries)
+ xRegistryConfigHeader, registries)
} else {
- logrus.Debugf("%q header found in request for username %q", XRegistryConfigHeader, auth.Username)
+ logrus.Debugf("%q header found in request for username %q", xRegistryConfigHeader, auth.Username)
}
}
- authfile, err := authConfigsToAuthFile(configs)
- return auth, authfile, err
+ return auth, configs, nil
}
-// getAuthCredentials extracts one or more DockerAuthConfigs from the request's
-// header. The header could specify a single-auth config in which case the
+// getAuthCredentials extracts one or more DockerAuthConfigs from an xRegistryAuthHeader
+// value. The header could specify a single-auth config in which case the
// first return value is set. In case of a multi-auth header, the contents are
-// stored in a temporary auth file (2nd return value). Note that the auth file
-// should be removed after usage.
-func getAuthCredentials(r *http.Request) (*types.DockerAuthConfig, string, error) {
+// returned in the second return value.
+func getAuthCredentials(headers []string) (*types.DockerAuthConfig, map[string]types.DockerAuthConfig, error) {
+ authHeader := headers[0]
+
// First look for a multi-auth header (i.e., a map).
- authConfigs, err := multiAuthHeader(r)
+ authConfigs, err := parseMultiAuthHeader(authHeader)
if err == nil {
- authfile, err := authConfigsToAuthFile(authConfigs)
- return nil, authfile, err
+ return nil, authConfigs, nil
}
// Fallback to looking for a single-auth header (i.e., one config).
- authConfigs, err = singleAuthHeader(r)
- if err != nil {
- return nil, "", err
- }
- var conf *types.DockerAuthConfig
- for k := range authConfigs {
- c := authConfigs[k]
- conf = &c
- break
- }
- return conf, "", nil
-}
-
-// Header builds the requested Authentication Header
-func Header(sys *types.SystemContext, headerName HeaderAuthName, authfile, username, password string) (map[string]string, error) {
- var (
- content string
- err error
- )
- switch headerName {
- case XRegistryAuthHeader:
- content, err = headerAuth(sys, authfile, username, password)
- case XRegistryConfigHeader:
- content, err = headerConfig(sys, authfile, username, password)
- default:
- err = fmt.Errorf("unsupported authentication header: %q", headerName)
- }
+ authConfig, err := parseSingleAuthHeader(authHeader)
if err != nil {
- return nil, err
+ return nil, nil, err
}
-
- if len(content) > 0 {
- return map[string]string{string(headerName): content}, nil
- }
- return nil, nil
+ return &authConfig, nil, nil
}
-// headerConfig returns a map with the XRegistryConfigHeader set which can
+// MakeXRegistryConfigHeader returns a map with the "X-Registry-Config" header set, which can
// conveniently be used in the http stack.
-func headerConfig(sys *types.SystemContext, authfile, username, password string) (string, error) {
+func MakeXRegistryConfigHeader(sys *types.SystemContext, username, password string) (map[string]string, error) {
if sys == nil {
sys = &types.SystemContext{}
}
- if authfile != "" {
- sys.AuthFilePath = authfile
- }
authConfigs, err := imageAuth.GetAllCredentials(sys)
if err != nil {
- return "", err
+ return nil, err
}
if username != "" {
@@ -174,29 +157,38 @@ func headerConfig(sys *types.SystemContext, authfile, username, password string)
}
if len(authConfigs) == 0 {
- return "", nil
+ return nil, nil
}
- return encodeMultiAuthConfigs(authConfigs)
+ content, err := encodeMultiAuthConfigs(authConfigs)
+ if err != nil {
+ return nil, err
+ }
+ return map[string]string{xRegistryConfigHeader: content}, nil
}
-// headerAuth returns a base64 encoded map with the XRegistryAuthHeader set which can
+// MakeXRegistryAuthHeader returns a map with the "X-Registry-Auth" header set, which can
// conveniently be used in the http stack.
-func headerAuth(sys *types.SystemContext, authfile, username, password string) (string, error) {
+func MakeXRegistryAuthHeader(sys *types.SystemContext, username, password string) (map[string]string, error) {
if username != "" {
- return encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
+ content, err := encodeSingleAuthConfig(types.DockerAuthConfig{Username: username, Password: password})
+ if err != nil {
+ return nil, err
+ }
+ return map[string]string{xRegistryAuthHeader: content}, nil
}
if sys == nil {
sys = &types.SystemContext{}
}
- if authfile != "" {
- sys.AuthFilePath = authfile
- }
authConfigs, err := imageAuth.GetAllCredentials(sys)
if err != nil {
- return "", err
+ return nil, err
+ }
+ content, err := encodeMultiAuthConfigs(authConfigs)
+ if err != nil {
+ return nil, err
}
- return encodeMultiAuthConfigs(authConfigs)
+ return map[string]string{xRegistryAuthHeader: content}, nil
}
// RemoveAuthfile is a convenience function that is meant to be called in a
@@ -258,34 +250,38 @@ func authConfigsToAuthFile(authConfigs map[string]types.DockerAuthConfig) (strin
// Now use the c/image packages to store the credentials. It's battle
// tested, and we make sure to use the same code as the image backend.
sys := types.SystemContext{AuthFilePath: authFilePath}
- for server, config := range authConfigs {
- server = normalize(server)
+ for authFileKey, config := range authConfigs {
+ key := normalizeAuthFileKey(authFileKey)
// Note that we do not validate the credentials here. We assume
// that all credentials are valid. They'll be used on demand
// later.
- if err := imageAuth.SetAuthentication(&sys, server, config.Username, config.Password); err != nil {
- return "", errors.Wrapf(err, "error storing credentials in temporary auth file (server: %q, user: %q)", server, config.Username)
+ if err := imageAuth.SetAuthentication(&sys, key, config.Username, config.Password); err != nil {
+ return "", errors.Wrapf(err, "error storing credentials in temporary auth file (key: %q / %q, user: %q)", authFileKey, key, config.Username)
}
}
return authFilePath, nil
}
-// normalize takes a server and removes the leading "http[s]://" prefix as well
-// as removes path suffixes from docker registries.
-func normalize(server string) string {
- stripped := strings.TrimPrefix(server, "http://")
+// normalizeAuthFileKey takes an auth file key and converts it into a new-style credential key
+// in the canonical format, as interpreted by c/image/pkg/docker/config.
+func normalizeAuthFileKey(authFileKey string) string {
+ stripped := strings.TrimPrefix(authFileKey, "http://")
stripped = strings.TrimPrefix(stripped, "https://")
- /// Normalize docker registries
- if strings.HasPrefix(stripped, "index.docker.io/") ||
- strings.HasPrefix(stripped, "registry-1.docker.io/") ||
- strings.HasPrefix(stripped, "docker.io/") {
+ if stripped != authFileKey { // URLs are interpreted to mean complete registries
stripped = strings.SplitN(stripped, "/", 2)[0]
}
- return stripped
+ // Only non-namespaced registry names (or URLs) need to be normalized; repo namespaces
+ // always use the simple format.
+ switch stripped {
+ case "registry-1.docker.io", "index.docker.io":
+ return "docker.io"
+ default:
+ return stripped
+ }
}
// dockerAuthToImageAuth converts a docker auth config to one we're using
@@ -309,28 +305,26 @@ func imageAuthToDockerAuth(authConfig types.DockerAuthConfig) dockerAPITypes.Aut
}
}
-// singleAuthHeader extracts a DockerAuthConfig from the request's header.
+// parseSingleAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value.
// The header content is a single DockerAuthConfig.
-func singleAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
- authHeader := r.Header.Get(string(XRegistryAuthHeader))
- authConfig := dockerAPITypes.AuthConfig{}
+func parseSingleAuthHeader(authHeader string) (types.DockerAuthConfig, error) {
// Accept "null" and handle it as empty value for compatibility reason with Docker.
// Some java docker clients pass this value, e.g. this one used in Eclipse.
- if len(authHeader) > 0 && authHeader != "null" {
- authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
- if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil {
- return nil, err
- }
+ if len(authHeader) == 0 || authHeader == "null" {
+ return types.DockerAuthConfig{}, nil
}
- authConfigs := make(map[string]types.DockerAuthConfig)
- authConfigs["0"] = dockerAuthToImageAuth(authConfig)
- return authConfigs, nil
+
+ authConfig := dockerAPITypes.AuthConfig{}
+ authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authHeader))
+ if err := json.NewDecoder(authJSON).Decode(&authConfig); err != nil {
+ return types.DockerAuthConfig{}, err
+ }
+ return dockerAuthToImageAuth(authConfig), nil
}
-// multiAuthHeader extracts a DockerAuthConfig from the request's header.
+// parseMultiAuthHeader extracts a DockerAuthConfig from an xRegistryAuthHeader value.
// The header content is a map[string]DockerAuthConfigs.
-func multiAuthHeader(r *http.Request) (map[string]types.DockerAuthConfig, error) {
- authHeader := r.Header.Get(string(XRegistryAuthHeader))
+func parseMultiAuthHeader(authHeader string) (map[string]types.DockerAuthConfig, error) {
// Accept "null" and handle it as empty value for compatibility reason with Docker.
// Some java docker clients pass this value, e.g. this one used in Eclipse.
if len(authHeader) == 0 || authHeader == "null" {
diff --git a/pkg/auth/auth_test.go b/pkg/auth/auth_test.go
index da2d9a5c5..f7e6e4ef6 100644
--- a/pkg/auth/auth_test.go
+++ b/pkg/auth/auth_test.go
@@ -1,13 +1,302 @@
package auth
import (
+ "encoding/base64"
+ "encoding/json"
"io/ioutil"
+ "net/http"
+ "os"
"testing"
+ "github.com/containers/image/v5/pkg/docker/config"
"github.com/containers/image/v5/types"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
+const largeAuthFile = `{"auths":{
+ "docker.io/vendor": {"auth": "ZG9ja2VyOnZlbmRvcg=="},
+ "https://index.docker.io/v1": {"auth": "ZG9ja2VyOnRvcA=="},
+ "quay.io/libpod": {"auth": "cXVheTpsaWJwb2Q="},
+ "quay.io": {"auth": "cXVheTp0b3A="}
+}}`
+
+// Semantics of largeAuthFile
+var largeAuthFileValues = map[string]types.DockerAuthConfig{
+ "docker.io/vendor": {Username: "docker", Password: "vendor"},
+ "docker.io": {Username: "docker", Password: "top"},
+ "quay.io/libpod": {Username: "quay", Password: "libpod"},
+ "quay.io": {Username: "quay", Password: "top"},
+}
+
+// systemContextForAuthFile returns a types.SystemContext with AuthFilePath pointing
+// to a temporary file with fileContents, or nil if fileContents is empty; and a cleanup
+// function the calle rmust arrange to call.
+func systemContextForAuthFile(t *testing.T, fileContents string) (*types.SystemContext, func()) {
+ if fileContents == "" {
+ return nil, func() {}
+ }
+
+ f, err := ioutil.TempFile("", "auth.json")
+ require.NoError(t, err)
+ path := f.Name()
+ err = ioutil.WriteFile(path, []byte(fileContents), 0700)
+ require.NoError(t, err)
+ return &types.SystemContext{AuthFilePath: path}, func() { os.Remove(path) }
+}
+
+// Test that GetCredentials() correctly parses what MakeXRegistryConfigHeader() produces
+func TestMakeXRegistryConfigHeaderGetCredentialsRoundtrip(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ fileContents string
+ username, password string
+ expectedOverride *types.DockerAuthConfig
+ expectedFileValues map[string]types.DockerAuthConfig
+ }{
+ {
+ name: "no data",
+ fileContents: "",
+ username: "",
+ password: "",
+ expectedOverride: nil,
+ expectedFileValues: nil,
+ },
+ {
+ name: "file data",
+ fileContents: largeAuthFile,
+ username: "",
+ password: "",
+ expectedOverride: nil,
+ expectedFileValues: largeAuthFileValues,
+ },
+ {
+ name: "file data + override",
+ fileContents: largeAuthFile,
+ username: "override-user",
+ password: "override-pass",
+ expectedOverride: &types.DockerAuthConfig{Username: "override-user", Password: "override-pass"},
+ expectedFileValues: largeAuthFileValues,
+ },
+ } {
+ sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
+ defer cleanup()
+ headers, err := MakeXRegistryConfigHeader(sys, tc.username, tc.password)
+ require.NoError(t, err)
+ req, err := http.NewRequest(http.MethodPost, "/", nil)
+ require.NoError(t, err, tc.name)
+ for k, v := range headers {
+ req.Header.Set(k, v)
+ }
+
+ override, resPath, err := GetCredentials(req)
+ require.NoError(t, err, tc.name)
+ defer RemoveAuthfile(resPath)
+ if tc.expectedOverride == nil {
+ assert.Nil(t, override, tc.name)
+ } else {
+ require.NotNil(t, override, tc.name)
+ assert.Equal(t, *tc.expectedOverride, *override, tc.name)
+ }
+ for key, expectedAuth := range tc.expectedFileValues {
+ auth, err := config.GetCredentials(&types.SystemContext{AuthFilePath: resPath}, key)
+ require.NoError(t, err, tc.name)
+ assert.Equal(t, expectedAuth, auth, "%s, key %s", tc.name, key)
+ }
+ }
+}
+
+// Test that GetCredentials() correctly parses what MakeXRegistryAuthHeader() produces
+func TestMakeXRegistryAuthHeaderGetCredentialsRoundtrip(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ fileContents string
+ username, password string
+ expectedOverride *types.DockerAuthConfig
+ expectedFileValues map[string]types.DockerAuthConfig
+ }{
+ {
+ name: "override",
+ fileContents: "",
+ username: "override-user",
+ password: "override-pass",
+ expectedOverride: &types.DockerAuthConfig{Username: "override-user", Password: "override-pass"},
+ expectedFileValues: nil,
+ },
+ {
+ name: "file data",
+ fileContents: largeAuthFile,
+ username: "",
+ password: "",
+ expectedFileValues: largeAuthFileValues,
+ },
+ } {
+ sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
+ defer cleanup()
+ headers, err := MakeXRegistryAuthHeader(sys, tc.username, tc.password)
+ require.NoError(t, err)
+ req, err := http.NewRequest(http.MethodPost, "/", nil)
+ require.NoError(t, err, tc.name)
+ for k, v := range headers {
+ req.Header.Set(k, v)
+ }
+
+ override, resPath, err := GetCredentials(req)
+ require.NoError(t, err, tc.name)
+ defer RemoveAuthfile(resPath)
+ if tc.expectedOverride == nil {
+ assert.Nil(t, override, tc.name)
+ } else {
+ require.NotNil(t, override, tc.name)
+ assert.Equal(t, *tc.expectedOverride, *override, tc.name)
+ }
+ for key, expectedAuth := range tc.expectedFileValues {
+ auth, err := config.GetCredentials(&types.SystemContext{AuthFilePath: resPath}, key)
+ require.NoError(t, err, tc.name)
+ assert.Equal(t, expectedAuth, auth, "%s, key %s", tc.name, key)
+ }
+ }
+}
+
+func TestMakeXRegistryConfigHeader(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ fileContents string
+ username, password string
+ shouldErr bool
+ expectedContents string
+ }{
+ {
+ name: "no data",
+ fileContents: "",
+ username: "",
+ password: "",
+ expectedContents: "",
+ },
+ {
+ name: "invalid JSON",
+ fileContents: "@invalid JSON",
+ username: "",
+ password: "",
+ shouldErr: true,
+ },
+ {
+ name: "file data",
+ fileContents: largeAuthFile,
+ username: "",
+ password: "",
+ expectedContents: `{
+ "docker.io/vendor": {"username": "docker", "password": "vendor"},
+ "docker.io": {"username": "docker", "password": "top"},
+ "quay.io/libpod": {"username": "quay", "password": "libpod"},
+ "quay.io": {"username": "quay", "password": "top"}
+ }`,
+ },
+ {
+ name: "file data + override",
+ fileContents: largeAuthFile,
+ username: "override-user",
+ password: "override-pass",
+ expectedContents: `{
+ "docker.io/vendor": {"username": "docker", "password": "vendor"},
+ "docker.io": {"username": "docker", "password": "top"},
+ "quay.io/libpod": {"username": "quay", "password": "libpod"},
+ "quay.io": {"username": "quay", "password": "top"},
+ "": {"username": "override-user", "password": "override-pass"}
+ }`,
+ },
+ } {
+ sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
+ defer cleanup()
+ res, err := MakeXRegistryConfigHeader(sys, tc.username, tc.password)
+ if tc.shouldErr {
+ assert.Error(t, err, tc.name)
+ } else {
+ require.NoError(t, err, tc.name)
+ if tc.expectedContents == "" {
+ assert.Empty(t, res, tc.name)
+ } else {
+ require.Len(t, res, 1, tc.name)
+ header, ok := res[xRegistryConfigHeader]
+ require.True(t, ok, tc.name)
+ decodedHeader, err := base64.URLEncoding.DecodeString(header)
+ require.NoError(t, err, tc.name)
+ // Don't test for a specific JSON representation, just for the expected contents.
+ expected := map[string]interface{}{}
+ actual := map[string]interface{}{}
+ err = json.Unmarshal([]byte(tc.expectedContents), &expected)
+ require.NoError(t, err, tc.name)
+ err = json.Unmarshal(decodedHeader, &actual)
+ require.NoError(t, err, tc.name)
+ assert.Equal(t, expected, actual, tc.name)
+ }
+ }
+ }
+}
+
+func TestMakeXRegistryAuthHeader(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ fileContents string
+ username, password string
+ shouldErr bool
+ expectedContents string
+ }{
+ {
+ name: "override",
+ fileContents: "",
+ username: "override-user",
+ password: "override-pass",
+ expectedContents: `{"username": "override-user", "password": "override-pass"}`,
+ },
+ {
+ name: "invalid JSON",
+ fileContents: "@invalid JSON",
+ username: "",
+ password: "",
+ shouldErr: true,
+ },
+ {
+ name: "file data",
+ fileContents: largeAuthFile,
+ username: "",
+ password: "",
+ expectedContents: `{
+ "docker.io/vendor": {"username": "docker", "password": "vendor"},
+ "docker.io": {"username": "docker", "password": "top"},
+ "quay.io/libpod": {"username": "quay", "password": "libpod"},
+ "quay.io": {"username": "quay", "password": "top"}
+ }`,
+ },
+ } {
+ sys, cleanup := systemContextForAuthFile(t, tc.fileContents)
+ defer cleanup()
+ res, err := MakeXRegistryAuthHeader(sys, tc.username, tc.password)
+ if tc.shouldErr {
+ assert.Error(t, err, tc.name)
+ } else {
+ require.NoError(t, err, tc.name)
+ if tc.expectedContents == "" {
+ assert.Empty(t, res, tc.name)
+ } else {
+ require.Len(t, res, 1, tc.name)
+ header, ok := res[xRegistryAuthHeader]
+ require.True(t, ok, tc.name)
+ decodedHeader, err := base64.URLEncoding.DecodeString(header)
+ require.NoError(t, err, tc.name)
+ // Don't test for a specific JSON representation, just for the expected contents.
+ expected := map[string]interface{}{}
+ actual := map[string]interface{}{}
+ err = json.Unmarshal([]byte(tc.expectedContents), &expected)
+ require.NoError(t, err, tc.name)
+ err = json.Unmarshal(decodedHeader, &actual)
+ require.NoError(t, err, tc.name)
+ assert.Equal(t, expected, actual, tc.name)
+ }
+ }
+ }
+}
+
func TestAuthConfigsToAuthFile(t *testing.T) {
for _, tc := range []struct {
name string
@@ -22,28 +311,28 @@ func TestAuthConfigsToAuthFile(t *testing.T) {
expectedContains: "{}",
},
{
- name: "registry with prefix",
+ name: "registry with a namespace prefix",
server: "my-registry.local/username",
shouldErr: false,
expectedContains: `"my-registry.local/username":`,
},
{
- name: "normalize https:// prefix",
+ name: "URLs are interpreted as full registries",
server: "http://my-registry.local/username",
shouldErr: false,
- expectedContains: `"my-registry.local/username":`,
+ expectedContains: `"my-registry.local":`,
},
{
- name: "normalize docker registry with https prefix",
+ name: "the old-style docker registry URL is normalized",
server: "http://index.docker.io/v1/",
shouldErr: false,
- expectedContains: `"index.docker.io":`,
+ expectedContains: `"docker.io":`,
},
{
- name: "normalize docker registry without https prefix",
- server: "docker.io/v2/",
+ name: "docker.io vendor namespace",
+ server: "docker.io/vendor",
shouldErr: false,
- expectedContains: `"docker.io":`,
+ expectedContains: `"docker.io/vendor":`,
},
} {
configs := map[string]types.DockerAuthConfig{}
@@ -54,13 +343,79 @@ func TestAuthConfigsToAuthFile(t *testing.T) {
filePath, err := authConfigsToAuthFile(configs)
if tc.shouldErr {
- assert.NotNil(t, err)
+ assert.Error(t, err)
assert.Empty(t, filePath)
} else {
- assert.Nil(t, err)
+ assert.NoError(t, err)
content, err := ioutil.ReadFile(filePath)
- assert.Nil(t, err)
+ require.NoError(t, err)
assert.Contains(t, string(content), tc.expectedContains)
+ os.Remove(filePath)
+ }
+ }
+}
+
+func TestParseSingleAuthHeader(t *testing.T) {
+ for _, tc := range []struct {
+ input string
+ shouldErr bool
+ expected types.DockerAuthConfig
+ }{
+ {
+ input: "", // An empty (or missing) header
+ expected: types.DockerAuthConfig{},
+ },
+ {
+ input: "null",
+ expected: types.DockerAuthConfig{},
+ },
+ // Invalid JSON
+ {input: "@", shouldErr: true},
+ // Success
+ {
+ input: base64.URLEncoding.EncodeToString([]byte(`{"username":"u1","password":"p1"}`)),
+ expected: types.DockerAuthConfig{Username: "u1", Password: "p1"},
+ },
+ } {
+ res, err := parseSingleAuthHeader(tc.input)
+ if tc.shouldErr {
+ assert.Error(t, err, tc.input)
+ } else {
+ require.NoError(t, err, tc.input)
+ assert.Equal(t, tc.expected, res, tc.input)
+ }
+ }
+}
+
+func TestParseMultiAuthHeader(t *testing.T) {
+ for _, tc := range []struct {
+ input string
+ shouldErr bool
+ expected map[string]types.DockerAuthConfig
+ }{
+ // Empty header
+ {input: "", expected: nil},
+ // "null"
+ {input: "null", expected: nil},
+ // Invalid JSON
+ {input: "@", shouldErr: true},
+ // Success
+ {
+ input: base64.URLEncoding.EncodeToString([]byte(
+ `{"https://index.docker.io/v1/":{"username":"u1","password":"p1"},` +
+ `"quay.io/libpod":{"username":"u2","password":"p2"}}`)),
+ expected: map[string]types.DockerAuthConfig{
+ "https://index.docker.io/v1/": {Username: "u1", Password: "p1"},
+ "quay.io/libpod": {Username: "u2", Password: "p2"},
+ },
+ },
+ } {
+ res, err := parseMultiAuthHeader(tc.input)
+ if tc.shouldErr {
+ assert.Error(t, err, tc.input)
+ } else {
+ require.NoError(t, err, tc.input)
+ assert.Equal(t, tc.expected, res, tc.input)
}
}
}
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index be6e5ab55..7bca43132 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -293,14 +293,10 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
headers map[string]string
err error
)
- if options.SystemContext == nil {
- headers, err = auth.Header(options.SystemContext, auth.XRegistryConfigHeader, "", "", "")
+ if options.SystemContext != nil && options.SystemContext.DockerAuthConfig != nil {
+ headers, err = auth.MakeXRegistryAuthHeader(options.SystemContext, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password)
} else {
- if options.SystemContext.DockerAuthConfig != nil {
- headers, err = auth.Header(options.SystemContext, auth.XRegistryAuthHeader, options.SystemContext.AuthFilePath, options.SystemContext.DockerAuthConfig.Username, options.SystemContext.DockerAuthConfig.Password)
- } else {
- headers, err = auth.Header(options.SystemContext, auth.XRegistryConfigHeader, options.SystemContext.AuthFilePath, "", "")
- }
+ headers, err = auth.MakeXRegistryConfigHeader(options.SystemContext, "", "")
}
if err != nil {
return nil, err
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index dfb500772..152ff0cde 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -8,6 +8,7 @@ import (
"net/url"
"strconv"
+ imageTypes "github.com/containers/image/v5/types"
"github.com/containers/podman/v3/pkg/api/handlers/types"
"github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/bindings"
@@ -280,7 +281,7 @@ func Push(ctx context.Context, source string, destination string, options *PushO
return err
}
// TODO: have a global system context we can pass around (1st argument)
- header, err := auth.Header(nil, auth.XRegistryAuthHeader, options.GetAuthfile(), options.GetUsername(), options.GetPassword())
+ header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
if err != nil {
return err
}
@@ -329,7 +330,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie
}
// TODO: have a global system context we can pass around (1st argument)
- header, err := auth.Header(nil, auth.XRegistryAuthHeader, options.GetAuthfile(), "", "")
+ header, err := auth.MakeXRegistryAuthHeader(&imageTypes.SystemContext{AuthFilePath: options.GetAuthfile()}, "", "")
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/images/pull.go b/pkg/bindings/images/pull.go
index be21aa593..ac583973f 100644
--- a/pkg/bindings/images/pull.go
+++ b/pkg/bindings/images/pull.go
@@ -10,6 +10,7 @@ import (
"os"
"strconv"
+ "github.com/containers/image/v5/types"
"github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities"
@@ -42,7 +43,7 @@ func Pull(ctx context.Context, rawImage string, options *PullOptions) ([]string,
}
// TODO: have a global system context we can pass around (1st argument)
- header, err := auth.Header(nil, auth.XRegistryAuthHeader, options.GetAuthfile(), options.GetUsername(), options.GetPassword())
+ header, err := auth.MakeXRegistryAuthHeader(&types.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
if err != nil {
return nil, err
}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
index 2cd7c3997..111a25cac 100644
--- a/pkg/bindings/play/play.go
+++ b/pkg/bindings/play/play.go
@@ -6,6 +6,7 @@ import (
"os"
"strconv"
+ "github.com/containers/image/v5/types"
"github.com/containers/podman/v3/pkg/auth"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/domain/entities"
@@ -40,7 +41,7 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla
}
// TODO: have a global system context we can pass around (1st argument)
- header, err := auth.Header(nil, auth.XRegistryAuthHeader, options.GetAuthfile(), options.GetUsername(), options.GetPassword())
+ header, err := auth.MakeXRegistryAuthHeader(&types.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword())
if err != nil {
return nil, err
}
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index d72f64b5e..bec505163 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -27,7 +27,7 @@ type ImageEngine interface {
ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error)
Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error
- Transfer(ctx context.Context, scpOpts ImageScpOptions) error
+ Transfer(ctx context.Context, source ImageScpOptions, dest ImageScpOptions, parentFlags []string) error
Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error)
Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error)
Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 8b0fd2b85..62e7f67c8 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -311,30 +311,28 @@ type ImageSaveOptions struct {
Quiet bool
}
-// ImageScpOptions provide options for securely copying images to podman remote
+// ImageScpOptions provide options for securely copying images to and from a remote host
type ImageScpOptions struct {
- // SoureImageName is the image the user is providing to load on a remote machine
- SourceImageName string
- // Tag allows for a new image to be created under the given name
- Tag string
- // ToRemote specifies that we are loading to the remote host
- ToRemote bool
- // FromRemote specifies that we are loading from the remote host
- FromRemote bool
+ // Remote determines if this entity is operating on a remote machine
+ Remote bool `json:"remote,omitempty"`
+ // File is the input/output file for the save and load Operation
+ File string `json:"file,omitempty"`
+ // Quiet Determines if the save and load operation will be done quietly
+ Quiet bool `json:"quiet,omitempty"`
+ // Image is the image the user is providing to save and load
+ Image string `json:"image,omitempty"`
+ // User is used in conjunction with Transfer to determine if a valid user was given to save from/load into
+ User string `json:"user,omitempty"`
+}
+
+// ImageScpConnections provides the ssh related information used in remote image transfer
+type ImageScpConnections struct {
// Connections holds the raw string values for connections (ssh or unix)
Connections []string
// URI contains the ssh connection URLs to be used by the client
URI []*url.URL
- // Iden contains ssh identity keys to be used by the client
- Iden []string
- // Save Options used for first half of the scp operation
- Save ImageSaveOptions
- // Load options used for the second half of the scp operation
- Load ImageLoadOptions
- // Rootless determines whether we are loading locally from root storage to rootless storage
- Rootless bool
- // User is used in conjunction with Rootless to determine which user to use to obtain the uid
- User string
+ // Identities contains ssh identity keys to be used by the client
+ Identities []string
}
// ImageTreeOptions provides options for ImageEngine.Tree()
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index f9850e5a8..1b5a1be51 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -138,6 +138,7 @@ type PodCreateOptions struct {
Userns specgen.Namespace `json:"-"`
Volume []string `json:"volume,omitempty"`
VolumesFrom []string `json:"volumes_from,omitempty"`
+ SecurityOpt []string `json:"security_opt,omitempty"`
}
// PodLogsOptions describes the options to extract pod logs.
@@ -230,7 +231,7 @@ type ContainerCreateOptions struct {
Rm bool
RootFS bool
Secrets []string
- SecurityOpt []string
+ SecurityOpt []string `json:"security_opt,omitempty"`
SdNotifyMode string
ShmSize string
SignaturePolicy string
@@ -312,6 +313,7 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod
s.Hostname = p.Hostname
s.Labels = p.Labels
s.Devices = p.Devices
+ s.SecurityOpt = p.SecurityOpt
s.NoInfra = !p.Infra
if p.InfraCommand != nil && len(*p.InfraCommand) > 0 {
s.InfraCommand = strings.Split(*p.InfraCommand, " ")
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 4346182d6..84c83ea8e 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -28,6 +28,7 @@ import (
domainUtils "github.com/containers/podman/v3/pkg/domain/utils"
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/rootless"
+ "github.com/containers/podman/v3/utils"
"github.com/containers/storage"
dockerRef "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
@@ -351,65 +352,19 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
return pushError
}
-// Transfer moves images from root to rootless storage so the user specified in the scp call can access and use the image modified by root
-func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
- if scpOpts.User == "" {
+// Transfer moves images between root and rootless storage so the user specified in the scp call can access and use the image modified by root
+func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
+ if source.User == "" {
return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage")
}
- var u *user.User
- scpOpts.User = strings.Split(scpOpts.User, ":")[0] // split in case provided with uid:gid
- _, err := strconv.Atoi(scpOpts.User)
- if err != nil {
- u, err = user.Lookup(scpOpts.User)
- if err != nil {
- return err
- }
- } else {
- u, err = user.LookupId(scpOpts.User)
- if err != nil {
- return err
- }
- }
- uid, err := strconv.Atoi(u.Uid)
- if err != nil {
- return err
- }
- gid, err := strconv.Atoi(u.Gid)
- if err != nil {
- return err
- }
- err = os.Chown(scpOpts.Save.Output, uid, gid) // chown the output because was created by root so we need to give th euser read access
- if err != nil {
- return err
- }
-
podman, err := os.Executable()
if err != nil {
return err
}
- machinectl, err := exec.LookPath("machinectl")
- if err != nil {
- logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
- cmd := exec.Command("su", "-l", u.Username, "--command", podman+" --log-level="+logrus.GetLevel().String()+" --cgroup-manager=cgroupfs load --input="+scpOpts.Save.Output) // load the new image to the rootless storage
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- logrus.Debug("Executing load command su")
- err = cmd.Run()
- if err != nil {
- return err
- }
- } else {
- cmd := exec.Command(machinectl, "shell", "-q", u.Username+"@.host", podman, "--log-level="+logrus.GetLevel().String(), "--cgroup-manager=cgroupfs", "load", "--input", scpOpts.Save.Output) // load the new image to the rootless storage
- cmd.Stderr = os.Stderr
- cmd.Stdout = os.Stdout
- logrus.Debug("Executing load command machinectl")
- err = cmd.Run()
- if err != nil {
- return err
- }
+ if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo
+ return transferRootless(source, dest, podman, parentFlags)
}
-
- return nil
+ return transferRootful(source, dest, podman, parentFlags)
}
func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error {
@@ -786,3 +741,123 @@ func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStore
}
return nil
}
+
+// TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users
+func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
+ var cmdSave *exec.Cmd
+ saveCommand := parentFlags
+ saveCommand = append(saveCommand, []string{"save", "--output", source.File, source.Image}...)
+
+ loadCommand := parentFlags
+ loadCommand = append(loadCommand, []string{"load", "--input", dest.File}...)
+
+ if source.User == "root" {
+ cmdSave = exec.Command("sudo", podman)
+ } else {
+ cmdSave = exec.Command(podman)
+ }
+ cmdSave = utils.CreateSCPCommand(cmdSave, saveCommand)
+ logrus.Debug("Executing save command")
+ err := cmdSave.Run()
+ if err != nil {
+ return err
+ }
+
+ var cmdLoad *exec.Cmd
+ if source.User != "root" {
+ cmdLoad = exec.Command("sudo", podman)
+ } else {
+ cmdLoad = exec.Command(podman)
+ }
+ cmdLoad = utils.CreateSCPCommand(cmdLoad, loadCommand)
+ logrus.Debug("Executing load command")
+ err = cmdLoad.Run()
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// TransferRootless creates new podman processes using exec.Command and su/machinectl, transferring images between the given source and destination users
+func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOptions, podman string, parentFlags []string) error {
+ basicCommand := []string{podman}
+ basicCommand = append(basicCommand, parentFlags...)
+ saveCommand := append(basicCommand, []string{"save", "--output", source.File, source.Image}...)
+ loadCommand := append(basicCommand, []string{"load", "--input", dest.File}...)
+ save := []string{strings.Join(saveCommand, " ")}
+ load := []string{strings.Join(loadCommand, " ")}
+
+ // if executing using sudo or transferring between two users, the TransferRootless approach will not work, default to using machinectl or su as necessary.
+ // the approach using sudo is preferable and more straightforward. There is no reason for using sudo in these situations
+ // since the feature is meant to transfer from root to rootless an vice versa without explicit sudo evocaiton.
+ var uSave *user.User
+ var uLoad *user.User
+ var err error
+ source.User = strings.Split(source.User, ":")[0] // split in case provided with uid:gid
+ dest.User = strings.Split(dest.User, ":")[0]
+ uSave, err = lookupUser(source.User)
+ if err != nil {
+ return err
+ }
+ switch {
+ case dest.User != "": // if we are given a destination user, check that first
+ uLoad, err = lookupUser(dest.User)
+ if err != nil {
+ return err
+ }
+ case uSave.Name != "root": // else if we have no destination user, and source is not root that means we should be root
+ uLoad, err = user.LookupId("0")
+ if err != nil {
+ return err
+ }
+ default: // else if we have no dest user, and source user IS root, we want to be the default user.
+ uString := os.Getenv("SUDO_USER")
+ if uString == "" {
+ return errors.New("$SUDO_USER must be defined to find the default rootless user")
+ }
+ uLoad, err = user.Lookup(uString)
+ if err != nil {
+ return err
+ }
+ }
+ machinectl, err := exec.LookPath("machinectl")
+ if err != nil {
+ logrus.Warn("defaulting to su since machinectl is not available, su will fail if no user session is available")
+ err = execSu(uSave, save)
+ if err != nil {
+ return err
+ }
+ return execSu(uLoad, load)
+ }
+ err = execMachine(uSave, saveCommand, machinectl)
+ if err != nil {
+ return err
+ }
+ return execMachine(uLoad, loadCommand, machinectl)
+}
+
+func lookupUser(u string) (*user.User, error) {
+ if u, err := user.LookupId(u); err == nil {
+ return u, nil
+ }
+ return user.Lookup(u)
+}
+
+func execSu(execUser *user.User, command []string) error {
+ cmd := exec.Command("su", "-l", execUser.Username, "--command")
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing command su")
+ return cmd.Run()
+}
+
+func execMachine(execUser *user.User, command []string, machinectl string) error {
+ var cmd *exec.Cmd
+ if execUser.Uid == "0" {
+ cmd = exec.Command("sudo", machinectl, "shell", "-q", execUser.Username+"@.host")
+ } else {
+ cmd = exec.Command(machinectl, "shell", "-q", execUser.Username+"@.host")
+ }
+ cmd = utils.CreateSCPCommand(cmd, command)
+ logrus.Debug("Executing command machinectl")
+ return cmd.Run()
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 2feb9d7ad..f26a489e6 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -123,7 +123,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.
return &entities.ImagePullReport{Images: pulledImages}, nil
}
-func (ir *ImageEngine) Transfer(ctx context.Context, scpOpts entities.ImageScpOptions) error {
+func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error {
return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage")
}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 139318977..84d3be296 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -7,7 +7,10 @@ import (
"fmt"
"io/ioutil"
"net/url"
+ "os"
"path/filepath"
+
+ "github.com/sirupsen/logrus"
)
/*
@@ -355,6 +358,56 @@ machine_enabled=true
},
})
+ // get certs for current user
+ userHome, err := os.UserHomeDir()
+ if err != nil {
+ logrus.Warnf("Unable to copy certs via ignition %s", err.Error())
+ return files
+ }
+
+ certFiles := getCerts(filepath.Join(userHome, ".config/containers/certs.d"))
+ files = append(files, certFiles...)
+
+ certFiles = getCerts(filepath.Join(userHome, ".config/docker/certs.d"))
+ files = append(files, certFiles...)
+
+ return files
+}
+
+func getCerts(certsDir string) []File {
+ var (
+ files []File
+ )
+
+ certs, err := ioutil.ReadDir(certsDir)
+ if err == nil {
+ for _, cert := range certs {
+ b, err := ioutil.ReadFile(filepath.Join(certsDir, cert.Name()))
+ if err != nil {
+ logrus.Warnf("Unable to read cert file %s", err.Error())
+ continue
+ }
+ files = append(files, File{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: filepath.Join("/etc/containers/certs.d/", cert.Name()),
+ User: getNodeUsr("root"),
+ },
+ FileEmbedded1: FileEmbedded1{
+ Append: nil,
+ Contents: Resource{
+ Source: encodeDataURLPtr(string(b)),
+ },
+ Mode: intToPtr(0644),
+ },
+ })
+ }
+ } else {
+ if !os.IsNotExist(err) {
+ logrus.Warnf("Unable to copy certs via ignition, error while reading certs from %s: %s", certsDir, err.Error())
+ }
+ }
+
return files
}
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index 92f331ce4..94bd40f86 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -244,7 +244,7 @@ can_use_shortcut ()
if (argv[argc+1] != NULL && (strcmp (argv[argc], "container") == 0 ||
strcmp (argv[argc], "image") == 0) &&
- strcmp (argv[argc+1], "mount") == 0)
+ (strcmp (argv[argc+1], "mount") == 0 || strcmp (argv[argc+1], "scp") == 0))
{
ret = false;
break;
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 7ab9d1b29..7d792b3b1 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -2,13 +2,14 @@ package generate
import (
"context"
- "fmt"
+ "encoding/json"
"path/filepath"
"strings"
cdi "github.com/container-orchestrated-devices/container-device-interface/pkg"
"github.com/containers/common/libimage"
"github.com/containers/podman/v3/libpod"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/namespaces"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
@@ -29,43 +30,30 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
// If joining a pod, retrieve the pod for use, and its infra container
var pod *libpod.Pod
- var infraConfig *libpod.ContainerConfig
+ var infra *libpod.Container
if s.Pod != "" {
pod, err = rt.LookupPod(s.Pod)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
}
if pod.HasInfraContainer() {
- infra, err := pod.InfraContainer()
+ infra, err = pod.InfraContainer()
if err != nil {
return nil, nil, nil, err
}
- infraConfig = infra.Config()
}
}
- if infraConfig != nil && (len(infraConfig.NamedVolumes) > 0 || len(infraConfig.UserVolumes) > 0 || len(infraConfig.ImageVolumes) > 0 || len(infraConfig.OverlayVolumes) > 0) {
- s.VolumesFrom = append(s.VolumesFrom, infraConfig.ID)
- }
-
- if infraConfig != nil && len(infraConfig.Spec.Linux.Devices) > 0 {
- s.DevicesFrom = append(s.DevicesFrom, infraConfig.ID)
- }
- if infraConfig != nil && infraConfig.Spec.Linux.Resources != nil && infraConfig.Spec.Linux.Resources.BlockIO != nil && len(infraConfig.Spec.Linux.Resources.BlockIO.ThrottleReadBpsDevice) > 0 {
- tempDev := make(map[string]spec.LinuxThrottleDevice)
- for _, val := range infraConfig.Spec.Linux.Resources.BlockIO.ThrottleReadBpsDevice {
- nodes, err := util.FindDeviceNodes()
- if err != nil {
- return nil, nil, nil, err
- }
- key := fmt.Sprintf("%d:%d", val.Major, val.Minor)
- tempDev[nodes[key]] = spec.LinuxThrottleDevice{Rate: uint64(val.Rate)}
- }
- for i, dev := range s.ThrottleReadBpsDevice {
- tempDev[i] = dev
+ options := []libpod.CtrCreateOption{}
+ compatibleOptions := &libpod.InfraInherit{}
+ var infraSpec *spec.Spec
+ if infra != nil {
+ options, infraSpec, compatibleOptions, err = Inherit(*infra)
+ if err != nil {
+ return nil, nil, nil, err
}
- s.ThrottleReadBpsDevice = tempDev
}
+
if err := FinishThrottleDevices(s); err != nil {
return nil, nil, nil, err
}
@@ -119,8 +107,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
s.CgroupNS = defaultNS
}
- options := []libpod.CtrCreateOption{}
-
if s.ContainerCreateCommand != nil {
options = append(options, libpod.WithCreateCommand(s.ContainerCreateCommand))
}
@@ -165,7 +151,8 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err
}
- opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command)
+ infraVolumes := (len(compatibleOptions.InfraVolumes) > 0 || len(compatibleOptions.InfraUserVolumes) > 0 || len(compatibleOptions.InfraImageVolumes) > 0)
+ opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, finalOverlays, imageData, command, infraVolumes, *compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
@@ -178,27 +165,29 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
logrus.Debugf("setting container name %s", s.Name)
options = append(options, libpod.WithName(s.Name))
}
- if len(s.DevicesFrom) > 0 {
- for _, dev := range s.DevicesFrom {
- ctr, err := rt.GetContainer(dev)
- if err != nil {
- return nil, nil, nil, err
- }
- devices := ctr.DeviceHostSrc()
- s.Devices = append(s.Devices, devices...)
- }
- }
if len(s.Devices) > 0 {
- opts = extractCDIDevices(s)
+ opts = ExtractCDIDevices(s)
options = append(options, opts...)
}
- runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command)
+ runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts, pod, command, compatibleOptions)
if err != nil {
return nil, nil, nil, err
}
if len(s.HostDeviceList) > 0 {
options = append(options, libpod.WithHostDevice(s.HostDeviceList))
}
+ if infraSpec != nil && infraSpec.Linux != nil { // if we are inheriting Linux info from a pod...
+ // Pass Security annotations
+ if len(infraSpec.Annotations[define.InspectAnnotationLabel]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationLabel]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationLabel] = infraSpec.Annotations[define.InspectAnnotationLabel]
+ }
+ if len(infraSpec.Annotations[define.InspectAnnotationSeccomp]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationSeccomp]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationSeccomp] = infraSpec.Annotations[define.InspectAnnotationSeccomp]
+ }
+ if len(infraSpec.Annotations[define.InspectAnnotationApparmor]) > 0 && len(runtimeSpec.Annotations[define.InspectAnnotationApparmor]) == 0 {
+ runtimeSpec.Annotations[define.InspectAnnotationApparmor] = infraSpec.Annotations[define.InspectAnnotationApparmor]
+ }
+ }
return runtimeSpec, s, options, err
}
func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Spec, s *specgen.SpecGenerator, infra bool, options ...libpod.CtrCreateOption) (*libpod.Container, error) {
@@ -210,7 +199,7 @@ func ExecuteCreate(ctx context.Context, rt *libpod.Runtime, runtimeSpec *spec.Sp
return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr)
}
-func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
+func ExtractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
devs := make([]spec.LinuxDevice, 0, len(s.Devices))
var cdiDevs []string
var options []libpod.CtrCreateOption
@@ -224,19 +213,16 @@ func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption {
cdiDevs = append(cdiDevs, device.Path)
continue
}
-
devs = append(devs, device)
}
-
s.Devices = devs
if len(cdiDevs) > 0 {
options = append(options, libpod.WithCDI(cdiDevs))
}
-
return options
}
-func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, overlays []*specgen.OverlayVolume, imageData *libimage.ImageData, command []string, infraVolumes bool, compatibleOptions libpod.InfraInherit) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -317,7 +303,10 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
for _, imageVolume := range s.ImageVolumes {
destinations = append(destinations, imageVolume.Destination)
}
- options = append(options, libpod.WithUserVolumes(destinations))
+
+ if len(destinations) > 0 || !infraVolumes {
+ options = append(options, libpod.WithUserVolumes(destinations))
+ }
if len(volumes) != 0 {
var vols []*libpod.ContainerNamedVolume
@@ -405,7 +394,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
} else {
- if pod != nil {
+ if pod != nil && len(compatibleOptions.InfraLabels) == 0 {
// duplicate the security options from the pod
processLabel, err := pod.ProcessLabel()
if err != nil {
@@ -498,3 +487,33 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
return options, nil
}
+
+func Inherit(infra libpod.Container) (opts []libpod.CtrCreateOption, infraS *spec.Spec, compat *libpod.InfraInherit, err error) {
+ options := []libpod.CtrCreateOption{}
+ compatibleOptions := &libpod.InfraInherit{}
+ infraConf := infra.Config()
+ infraSpec := infraConf.Spec
+
+ config, err := json.Marshal(infraConf)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ err = json.Unmarshal(config, compatibleOptions)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ if infraSpec.Linux != nil && infraSpec.Linux.Resources != nil {
+ resources, err := json.Marshal(infraSpec.Linux.Resources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ err = json.Unmarshal(resources, &compatibleOptions.InfraResources)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ }
+ if compatibleOptions != nil {
+ options = append(options, libpod.WithInfraConfig(*compatibleOptions))
+ }
+ return options, infraSpec, compatibleOptions, nil
+}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index efac53104..ee3a990fc 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -2,6 +2,7 @@ package generate
import (
"context"
+ "encoding/json"
"path"
"strings"
@@ -174,7 +175,7 @@ func getCGroupPermissons(unmask []string) string {
}
// SpecGenToOCI returns the base configuration for the container.
-func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string) (*spec.Spec, error) {
+func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *libimage.Image, mounts []spec.Mount, pod *libpod.Pod, finalCmd []string, compatibleOptions *libpod.InfraInherit) (*spec.Spec, error) {
cgroupPerm := getCGroupPermissons(s.Unmask)
g, err := generate.New("linux")
@@ -299,9 +300,32 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
g.AddAnnotation(key, val)
}
- g.Config.Linux.Resources = s.ResourceLimits
+ if compatibleOptions.InfraResources == nil && s.ResourceLimits != nil {
+ g.Config.Linux.Resources = s.ResourceLimits
+ } else if s.ResourceLimits != nil { // if we have predefined resource limits we need to make sure we keep the infra and container limits
+ originalResources, err := json.Marshal(s.ResourceLimits)
+ if err != nil {
+ return nil, err
+ }
+ infraResources, err := json.Marshal(compatibleOptions.InfraResources)
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(infraResources, s.ResourceLimits) // put infra's resource limits in the container
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(originalResources, s.ResourceLimits) // make sure we did not override anything
+ if err != nil {
+ return nil, err
+ }
+ g.Config.Linux.Resources = s.ResourceLimits
+ } else {
+ g.Config.Linux.Resources = compatibleOptions.InfraResources
+ }
// Devices
+ var userDevices []spec.LinuxDevice
if s.Privileged {
// If privileged, we need to add all the host devices to the
// spec. We do not add the user provided ones because we are
@@ -316,14 +340,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
return nil, err
}
}
+ if len(compatibleOptions.InfraDevices) > 0 && len(s.Devices) == 0 {
+ userDevices = compatibleOptions.InfraDevices
+ } else {
+ userDevices = s.Devices
+ }
// add default devices specified by caller
- for _, device := range s.Devices {
+ for _, device := range userDevices {
if err = DevicesFromPath(&g, device.Path); err != nil {
return nil, err
}
}
}
- s.HostDeviceList = s.Devices
+ s.HostDeviceList = userDevices
// set the devices cgroup when not running in a user namespace
if !inUserNS && !s.Privileged {
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index e59d11c0a..33e8422fd 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -196,6 +196,7 @@ type PodSpecGenerator struct {
PodCgroupConfig
PodResourceConfig
PodStorageConfig
+ PodSecurityConfig
InfraContainerSpec *SpecGenerator `json:"-"`
}
@@ -210,6 +211,10 @@ type PodResourceConfig struct {
ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"`
}
+type PodSecurityConfig struct {
+ SecurityOpt []string `json:"security_opt,omitempty"`
+}
+
// NewPodSpecGenerator creates a new pod spec
func NewPodSpecGenerator() *PodSpecGenerator {
return &PodSpecGenerator{}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 390057c32..11edf265f 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -665,8 +665,8 @@ func CreateCidFile(cidfile string, id string) error {
return nil
}
-// DefaultCPUPeriod is the default CPU period is 100us, which is the same default
-// as Kubernetes.
+// DefaultCPUPeriod is the default CPU period (100ms) in microseconds, which is
+// the same default as Kubernetes.
const DefaultCPUPeriod uint64 = 100000
// CoresToPeriodAndQuota converts a fraction of cores to the equivalent