summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers.go13
-rw-r--r--pkg/api/handlers/compat/events.go2
-rw-r--r--pkg/api/handlers/compat/images_build.go10
-rw-r--r--pkg/api/handlers/compat/info.go1
-rw-r--r--pkg/api/handlers/compat/networks.go52
-rw-r--r--pkg/api/handlers/compat/swagger.go10
-rw-r--r--pkg/api/handlers/libpod/containers_create.go6
-rw-r--r--pkg/api/handlers/libpod/networks.go2
-rw-r--r--pkg/api/handlers/libpod/play.go4
-rw-r--r--pkg/api/handlers/libpod/pods.go1
-rw-r--r--pkg/api/handlers/libpod/swagger.go6
-rw-r--r--pkg/api/handlers/utils/images.go2
-rw-r--r--pkg/api/server/docs.go4
-rw-r--r--pkg/api/server/register_networks.go10
-rw-r--r--pkg/api/server/register_play.go6
-rw-r--r--pkg/api/server/register_volumes.go4
-rw-r--r--pkg/bindings/network/network.go25
-rw-r--r--pkg/bindings/network/types.go13
-rw-r--r--pkg/bindings/network/types_connect_options.go33
-rw-r--r--pkg/bindings/play/types.go4
-rw-r--r--pkg/bindings/play/types_kube_options.go6
-rw-r--r--pkg/bindings/test/images_test.go16
-rw-r--r--pkg/checkpoint/checkpoint_restore.go12
-rw-r--r--pkg/criu/criu.go22
-rw-r--r--pkg/criu/criu_unsupported.go7
-rw-r--r--pkg/domain/entities/containers.go1
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/images.go36
-rw-r--r--pkg/domain/entities/network.go6
-rw-r--r--pkg/domain/entities/play.go4
-rw-r--r--pkg/domain/entities/pods.go12
-rw-r--r--pkg/domain/entities/types.go22
-rw-r--r--pkg/domain/filters/containers.go20
-rw-r--r--pkg/domain/filters/pods.go22
-rw-r--r--pkg/domain/infra/abi/containers.go1
-rw-r--r--pkg/domain/infra/abi/images.go179
-rw-r--r--pkg/domain/infra/abi/network.go6
-rw-r--r--pkg/domain/infra/abi/play.go53
-rw-r--r--pkg/domain/infra/abi/pods.go4
-rw-r--r--pkg/domain/infra/tunnel/images.go2
-rw-r--r--pkg/domain/infra/tunnel/network.go3
-rw-r--r--pkg/domain/infra/tunnel/play.go2
-rw-r--r--pkg/machine/config.go17
-rw-r--r--pkg/machine/connection.go3
-rw-r--r--pkg/machine/fcos.go29
-rw-r--r--pkg/machine/fedora.go122
-rw-r--r--pkg/machine/ignition.go88
-rw-r--r--pkg/machine/ignition_darwin.go16
-rw-r--r--pkg/machine/ignition_linux.go15
-rw-r--r--pkg/machine/ignition_schema.go2
-rw-r--r--pkg/machine/ignition_windows.go7
-rw-r--r--pkg/machine/keys.go47
-rw-r--r--pkg/machine/machine_unsupported.go2
-rw-r--r--pkg/machine/pull.go96
-rw-r--r--pkg/machine/qemu/config.go4
-rw-r--r--pkg/machine/qemu/machine.go74
-rw-r--r--pkg/machine/qemu/machine_unsupported.go2
-rw-r--r--pkg/machine/wsl/machine.go1119
-rw-r--r--pkg/machine/wsl/machine_unsupported.go3
-rw-r--r--pkg/machine/wsl/util_windows.go338
-rw-r--r--pkg/network/network.go27
-rw-r--r--pkg/ps/ps.go4
-rw-r--r--pkg/rootless/rootless_linux.c2
-rw-r--r--pkg/rootless/rootless_linux.go4
-rw-r--r--pkg/specgen/container_validate.go22
-rw-r--r--pkg/specgen/generate/container.go4
-rw-r--r--pkg/specgen/generate/container_create.go129
-rw-r--r--pkg/specgen/generate/namespaces.go40
-rw-r--r--pkg/specgen/generate/oci.go45
-rw-r--r--pkg/specgen/generate/pod_create.go18
-rw-r--r--pkg/specgen/generate/validate.go4
-rw-r--r--pkg/specgen/namespaces.go177
-rw-r--r--pkg/specgen/namespaces_test.go265
-rw-r--r--pkg/specgen/pod_validate.go23
-rw-r--r--pkg/specgen/podspecgen.go40
-rw-r--r--pkg/specgen/specgen.go29
-rw-r--r--pkg/specgenutil/specgen.go30
-rw-r--r--pkg/util/utils.go12
78 files changed, 2936 insertions, 569 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 4f101ce84..ad341c3ab 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -356,11 +356,20 @@ 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())},
Image: imageName,
- ImageID: imageID,
+ ImageID: "sha256:" + imageID,
Command: strings.Join(l.Command(), " "),
Created: l.CreatedTime().Unix(),
Ports: ports,
@@ -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_build.go b/pkg/api/handlers/compat/images_build.go
index 2fd5dcccd..0fcac5330 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -621,7 +621,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
Stream string `json:"stream,omitempty"`
Error *jsonmessage.JSONError `json:"errorDetail,omitempty"`
// NOTE: `error` is being deprecated check https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go#L148
- ErrorMessage string `json:"error,omitempty"` // deprecate this slowly
+ ErrorMessage string `json:"error,omitempty"` // deprecate this slowly
+ Aux json.RawMessage `json:"aux,omitempty"`
}{}
select {
@@ -655,7 +656,12 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
return
case <-runCtx.Done():
if success {
- if !utils.IsLibpodRequest(r) {
+ if !utils.IsLibpodRequest(r) && !query.Quiet {
+ m.Aux = []byte(fmt.Sprintf(`{"ID":"sha256:%s"}`, imageID))
+ if err := enc.Encode(m); err != nil {
+ logrus.Warnf("failed to json encode error %v", err)
+ }
+ m.Aux = nil
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
if err := enc.Encode(m); err != nil {
logrus.Warnf("Failed to json encode error %v", err)
diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go
index 941718a8b..777009f0a 100644
--- a/pkg/api/handlers/compat/info.go
+++ b/pkg/api/handlers/compat/info.go
@@ -84,7 +84,6 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
InitBinary: "",
InitCommit: docker.Commit{},
Isolation: "",
- KernelMemory: sysInfo.KernelMemory,
KernelMemoryTCP: false,
KernelVersion: infoData.Host.Kernel,
Labels: nil,
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index 8aab29658..db3af7d0b 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -299,20 +299,66 @@ func Connect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
var (
- aliases []string
netConnect types.NetworkConnect
)
if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
+
+ netOpts := nettypes.PerNetworkOptions{}
+
name := utils.GetName(r)
if netConnect.EndpointConfig != nil {
if netConnect.EndpointConfig.Aliases != nil {
- aliases = netConnect.EndpointConfig.Aliases
+ netOpts.Aliases = netConnect.EndpointConfig.Aliases
+ }
+
+ // if IP address is provided
+ if len(netConnect.EndpointConfig.IPAddress) > 0 {
+ staticIP := net.ParseIP(netConnect.EndpointConfig.IPAddress)
+ if staticIP == nil {
+ utils.Error(w, "failed to parse the ip address", http.StatusInternalServerError,
+ errors.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress))
+ return
+ }
+ netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
+ }
+
+ if netConnect.EndpointConfig.IPAMConfig != nil {
+ // if IPAMConfig.IPv4Address is provided
+ if len(netConnect.EndpointConfig.IPAMConfig.IPv4Address) > 0 {
+ staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv4Address)
+ if staticIP == nil {
+ utils.Error(w, "failed to parse the ipv4 address", http.StatusInternalServerError,
+ errors.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address))
+ return
+ }
+ netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
+ }
+ // if IPAMConfig.IPv6Address is provided
+ if len(netConnect.EndpointConfig.IPAMConfig.IPv6Address) > 0 {
+ staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv6Address)
+ if staticIP == nil {
+ utils.Error(w, "failed to parse the ipv6 address", http.StatusInternalServerError,
+ errors.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
+ return
+ }
+ netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP)
+ }
+ }
+ // If MAC address is provided
+ if len(netConnect.EndpointConfig.MacAddress) > 0 {
+ staticMac, err := net.ParseMAC(netConnect.EndpointConfig.MacAddress)
+ if err != nil {
+ utils.Error(w, "failed to parse the mac address", http.StatusInternalServerError,
+ errors.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address))
+ return
+ }
+ netOpts.StaticMAC = nettypes.HardwareAddr(staticMac)
}
}
- err := runtime.ConnectContainerToNetwork(netConnect.Container, name, aliases)
+ err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netOpts)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, netConnect.Container, err)
diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go
index cfbdd1154..32167d6b5 100644
--- a/pkg/api/handlers/compat/swagger.go
+++ b/pkg/api/handlers/compat/swagger.go
@@ -55,15 +55,13 @@ type swagCompatNetworkCreateResponse struct {
}
// Network disconnect
-// swagger:model NetworkConnectRequest
+// swagger:model NetworkCompatConnectRequest
type swagCompatNetworkConnectRequest struct {
- // in:body
- Body struct{ types.NetworkConnect }
+ types.NetworkConnect
}
// Network disconnect
-// swagger:model NetworkDisconnectRequest
+// swagger:model NetworkCompatDisconnectRequest
type swagCompatNetworkDisconnectRequest struct {
- // in:body
- Body struct{ types.NetworkDisconnect }
+ types.NetworkDisconnect
}
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index 77bfe7b50..d1841769a 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -19,11 +19,15 @@ import (
func CreateContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
var sg specgen.SpecGenerator
+
if err := json.NewDecoder(r.Body).Decode(&sg); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
-
+ if sg.Passwd == nil {
+ t := true
+ sg.Passwd = &t
+ }
warn, err := generate.CompleteSpec(r.Context(), runtime, &sg)
if err != nil {
utils.InternalServerError(w, err)
diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go
index 1f7f2e26c..a28c3c57c 100644
--- a/pkg/api/handlers/libpod/networks.go
+++ b/pkg/api/handlers/libpod/networks.go
@@ -125,7 +125,7 @@ func Connect(w http.ResponseWriter, r *http.Request) {
return
}
name := utils.GetName(r)
- err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.Aliases)
+ err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.PerNetworkOptions)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, netConnect.Container, err)
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
index e6ae9ad18..6ef83ad92 100644
--- a/pkg/api/handlers/libpod/play.go
+++ b/pkg/api/handlers/libpod/play.go
@@ -23,7 +23,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
query := struct {
- Network string `schema:"network"`
+ Network []string `schema:"network"`
TLSVerify bool `schema:"tlsVerify"`
LogDriver string `schema:"logDriver"`
LogOptions []string `schema:"logOptions"`
@@ -103,7 +103,7 @@ func PlayKube(w http.ResponseWriter, r *http.Request) {
Authfile: authfile,
Username: username,
Password: password,
- Network: query.Network,
+ Networks: query.Network,
NoHosts: query.NoHosts,
Quiet: true,
LogDriver: query.LogDriver,
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/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index 7ccfdd0f3..8d7058b1e 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -133,6 +133,12 @@ type swagNetworkPruneResponse struct {
Body []entities.NetworkPruneReport
}
+// Network connect
+// swagger:model NetworkConnectRequest
+type swagNetworkConnectRequest struct {
+ entities.NetworkConnectOptions
+}
+
func ServeSwagger(w http.ResponseWriter, r *http.Request) {
path := DefaultPodmanSwaggerSpec
if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found {
diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go
index d874165e3..3f3f48193 100644
--- a/pkg/api/handlers/utils/images.go
+++ b/pkg/api/handlers/utils/images.go
@@ -35,7 +35,7 @@ func NormalizeToDockerHub(r *http.Request, nameOrID string) (string, error) {
if errors.Cause(err) != storage.ErrImageUnknown {
return "", fmt.Errorf("normalizing name for compat API: %v", err)
}
- } else if strings.HasPrefix(img.ID(), nameOrID) {
+ } else if strings.HasPrefix(img.ID(), strings.TrimPrefix(nameOrID, "sha256:")) {
return img.ID(), nil
}
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 641bce333..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:
@@ -131,7 +131,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// name: create
// description: attributes for connecting a container to a network
// schema:
- // $ref: "#/definitions/NetworkConnectRequest"
+ // $ref: "#/definitions/NetworkCompatConnectRequest"
// responses:
// 200:
// description: OK
@@ -159,7 +159,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// name: create
// description: attributes for disconnecting a container from a network
// schema:
- // $ref: "#/definitions/NetworkDisconnectRequest"
+ // $ref: "#/definitions/NetworkCompatDisconnectRequest"
// responses:
// 200:
// description: OK
@@ -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:
@@ -368,7 +368,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// name: create
// description: attributes for disconnecting a container from a network
// schema:
- // $ref: "#/definitions/NetworkDisconnectRequest"
+ // $ref: "#/definitions/NetworkCompatDisconnectRequest"
// responses:
// 200:
// description: OK
diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go
index 915d0d02e..5ace01929 100644
--- a/pkg/api/server/register_play.go
+++ b/pkg/api/server/register_play.go
@@ -18,8 +18,10 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// parameters:
// - in: query
// name: network
- // type: string
- // description: Connect the pod to this network.
+ // type: array
+ // description: USe the network mode or specify an array of networks.
+ // items:
+ // type: string
// - in: query
// name: tlsVerify
// type: boolean
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/bindings/network/network.go b/pkg/bindings/network/network.go
index 172598be1..66e01a016 100644
--- a/pkg/bindings/network/network.go
+++ b/pkg/bindings/network/network.go
@@ -3,7 +3,6 @@ package network
import (
"context"
"net/http"
- "net/url"
"strings"
"github.com/containers/podman/v3/libpod/network/types"
@@ -110,8 +109,6 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin
if err != nil {
return err
}
- // No params are used for disconnect
- params := url.Values{}
// Disconnect sends everything in body
disconnect := struct {
Container string
@@ -128,7 +125,7 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin
return err
}
stringReader := strings.NewReader(body)
- response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/disconnect", nil, nil, networkName)
if err != nil {
return err
}
@@ -138,32 +135,26 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin
}
// Connect adds a container to a network
-func Connect(ctx context.Context, networkName string, ContainerNameOrID string, options *ConnectOptions) error {
+func Connect(ctx context.Context, networkName string, containerNameOrID string, options *types.PerNetworkOptions) error {
if options == nil {
- options = new(ConnectOptions)
+ options = new(types.PerNetworkOptions)
}
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
- // No params are used in connect
- params := url.Values{}
// Connect sends everything in body
- connect := struct {
- Container string
- Aliases []string
- }{
- Container: ContainerNameOrID,
- }
- if aliases := options.GetAliases(); options.Changed("Aliases") {
- connect.Aliases = aliases
+ connect := entities.NetworkConnectOptions{
+ Container: containerNameOrID,
+ PerNetworkOptions: *options,
}
+
body, err := jsoniter.MarshalToString(connect)
if err != nil {
return err
}
stringReader := strings.NewReader(body)
- response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName)
+ response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/networks/%s/connect", nil, nil, networkName)
if err != nil {
return err
}
diff --git a/pkg/bindings/network/types.go b/pkg/bindings/network/types.go
index 8088de061..b82c0e438 100644
--- a/pkg/bindings/network/types.go
+++ b/pkg/bindings/network/types.go
@@ -1,6 +1,8 @@
package network
-import "net"
+import (
+ "net"
+)
//go:generate go run ../generator/generator.go CreateOptions
// CreateOptions are optional options for creating networks
@@ -61,15 +63,6 @@ type DisconnectOptions struct {
Force *bool
}
-//go:generate go run ../generator/generator.go ConnectOptions
-// ConnectOptions are optional options for connecting
-// containers from a network
-type ConnectOptions struct {
- // Aliases are names the container will be known as
- // when using the dns plugin
- Aliases *[]string
-}
-
//go:generate go run ../generator/generator.go ExistsOptions
// ExistsOptions are optional options for checking
// if a network exists
diff --git a/pkg/bindings/network/types_connect_options.go b/pkg/bindings/network/types_connect_options.go
deleted file mode 100644
index b7a465999..000000000
--- a/pkg/bindings/network/types_connect_options.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Code generated by go generate; DO NOT EDIT.
-package network
-
-import (
- "net/url"
-
- "github.com/containers/podman/v3/pkg/bindings/internal/util"
-)
-
-// Changed returns true if named field has been set
-func (o *ConnectOptions) Changed(fieldName string) bool {
- return util.Changed(o, fieldName)
-}
-
-// ToParams formats struct fields to be passed to API service
-func (o *ConnectOptions) ToParams() (url.Values, error) {
- return util.ToParams(o)
-}
-
-// WithAliases set field Aliases to given value
-func (o *ConnectOptions) WithAliases(value []string) *ConnectOptions {
- o.Aliases = &value
- return o
-}
-
-// GetAliases returns value of field Aliases
-func (o *ConnectOptions) GetAliases() []string {
- if o.Aliases == nil {
- var z []string
- return z
- }
- return *o.Aliases
-}
diff --git a/pkg/bindings/play/types.go b/pkg/bindings/play/types.go
index 011f7f9ca..ca639e46b 100644
--- a/pkg/bindings/play/types.go
+++ b/pkg/bindings/play/types.go
@@ -15,8 +15,8 @@ type KubeOptions struct {
Username *string
// Password for authenticating against the registry.
Password *string
- // Network - name of the CNI network to connect to.
- Network *string
+ // Network - name of the networks to connect to.
+ Network *[]string
// NoHosts - do not generate /etc/hosts file in pod's containers
NoHosts *bool
// Quiet - suppress output when pulling images.
diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/play/types_kube_options.go
index 344771e0c..593f026a3 100644
--- a/pkg/bindings/play/types_kube_options.go
+++ b/pkg/bindings/play/types_kube_options.go
@@ -79,15 +79,15 @@ func (o *KubeOptions) GetPassword() string {
}
// WithNetwork set field Network to given value
-func (o *KubeOptions) WithNetwork(value string) *KubeOptions {
+func (o *KubeOptions) WithNetwork(value []string) *KubeOptions {
o.Network = &value
return o
}
// GetNetwork returns value of field Network
-func (o *KubeOptions) GetNetwork() string {
+func (o *KubeOptions) GetNetwork() []string {
if o.Network == nil {
- var z string
+ var z []string
return z
}
return *o.Network
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 8489e6ff1..4ee824472 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -85,14 +85,16 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
- // Remove invalid image should be a 404
+ // NOTE that removing an image that does not exist will still
+ // return a 200 http status. The response, however, includes
+ // the exit code that podman-remote should exit with.
+ //
+ // The libpod/images/remove endpoint supports batch removal of
+ // images for performance reasons and for hiding the logic of
+ // deciding which exit code to use from the client.
response, errs := images.Remove(bt.conn, []string{"foobar5000"}, nil)
Expect(len(errs)).To(BeNumerically(">", 0))
- code, _ := bindings.CheckResponseCode(errs[0])
- // FIXME FIXME FIXME: #12441: THIS IS BROKEN
- // FIXME FIXME FIXME: we get msg: "foobar5000: image not known"
- // FIXME FIXME FIXME: ...with no ResponseCode
- Expect(code).To(BeNumerically("==", -1))
+ Expect(response.ExitCode).To(BeNumerically("==", 1)) // podman-remote would exit with 1
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
@@ -102,7 +104,7 @@ var _ = Describe("Podman images", func() {
Expect(inspectData.ID).To(Equal(response.Deleted[0]))
inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
- code, _ = bindings.CheckResponseCode(err)
+ code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Start a container with alpine image
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index c371adf5b..34bd8a124 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -78,6 +78,18 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
}
}
+ if restoreOptions.IgnoreStaticIP || restoreOptions.IgnoreStaticMAC {
+ for net, opts := range ctrConfig.Networks {
+ if restoreOptions.IgnoreStaticIP {
+ opts.StaticIPs = nil
+ }
+ if restoreOptions.IgnoreStaticMAC {
+ opts.StaticMAC = nil
+ }
+ ctrConfig.Networks[net] = opts
+ }
+ }
+
ctrID := ctrConfig.ID
newName := false
diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go
index 2a6805979..967da0dca 100644
--- a/pkg/criu/criu.go
+++ b/pkg/criu/criu.go
@@ -1,7 +1,12 @@
+// +build linux
+
package criu
import (
"github.com/checkpoint-restore/go-criu/v5"
+ "github.com/checkpoint-restore/go-criu/v5/rpc"
+
+ "google.golang.org/protobuf/proto"
)
// MinCriuVersion for Podman at least CRIU 3.11 is required
@@ -21,3 +26,20 @@ func CheckForCriu(version int) bool {
}
return result
}
+
+func MemTrack() bool {
+ features, err := criu.MakeCriu().FeatureCheck(
+ &rpc.CriuFeatures{
+ MemTrack: proto.Bool(true),
+ },
+ )
+ if err != nil {
+ return false
+ }
+
+ if features == nil || features.MemTrack == nil {
+ return false
+ }
+
+ return *features.MemTrack
+}
diff --git a/pkg/criu/criu_unsupported.go b/pkg/criu/criu_unsupported.go
new file mode 100644
index 000000000..51cd0c1fd
--- /dev/null
+++ b/pkg/criu/criu_unsupported.go
@@ -0,0 +1,7 @@
+// +build !linux
+
+package criu
+
+func MemTrack() bool {
+ return false
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 1677c067f..ae441b7f3 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -341,6 +341,7 @@ type ContainerRunOptions struct {
Rm bool
SigProxy bool
Spec *specgen.SpecGenerator
+ Passwd bool
}
// ContainerRunReport describes the results of running
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/network.go b/pkg/domain/entities/network.go
index d7389a699..34b89ae7d 100644
--- a/pkg/domain/entities/network.go
+++ b/pkg/domain/entities/network.go
@@ -2,6 +2,8 @@ package entities
import (
"net"
+
+ "github.com/containers/podman/v3/libpod/network/types"
)
// NetworkListOptions describes options for listing networks in cli
@@ -67,8 +69,8 @@ type NetworkDisconnectOptions struct {
// NetworkConnectOptions describes options for connecting
// a container to a network
type NetworkConnectOptions struct {
- Aliases []string
- Container string
+ Container string `json:"container"`
+ types.PerNetworkOptions
}
// NetworkPruneReport containers the name of network and an error
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
index ad35dfe25..39234caf8 100644
--- a/pkg/domain/entities/play.go
+++ b/pkg/domain/entities/play.go
@@ -26,8 +26,8 @@ type PlayKubeOptions struct {
Username string
// Password for authenticating against the registry.
Password string
- // Network - name of the CNI network to connect to.
- Network string
+ // Networks - name of the network to connect to.
+ Networks []string
// Quiet - suppress output when pulling images.
Quiet bool
// SignaturePolicy - path to a signature-policy file.
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index b255785c2..1b5a1be51 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -7,7 +7,6 @@ import (
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/podman/v3/libpod/define"
- "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -139,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.
@@ -190,13 +190,13 @@ type ContainerCreateOptions struct {
HealthTimeout string
Hostname string `json:"hostname,omitempty"`
HTTPProxy bool
+ HostUsers []string
ImageVolume string
Init bool
InitContainerType string
InitPath string
Interactive bool
IPC string
- KernelMemory string
Label []string
LabelFile []string
LogDriver string
@@ -231,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
@@ -313,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, " ")
@@ -329,11 +330,8 @@ func ToPodSpecGen(s specgen.PodSpecGenerator, p *PodCreateOptions) (*specgen.Pod
if p.Net != nil {
s.NetNS = p.Net.Network
- s.StaticIP = p.Net.StaticIP
- // type cast to types.HardwareAddr
- s.StaticMAC = (*types.HardwareAddr)(p.Net.StaticMAC)
s.PortMappings = p.Net.PublishPorts
- s.CNINetworks = p.Net.CNINetworks
+ s.Networks = p.Net.Networks
s.NetworkOptions = p.Net.NetworkOptions
if p.Net.UseImageResolvConf {
s.NoManageResolvConf = true
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index e062b9442..0348c0af5 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -45,18 +45,16 @@ type NetFlags struct {
// NetOptions reflect the shared network options between
// pods and containers
type NetOptions struct {
- AddHosts []string `json:"hostadd,omitempty"`
- Aliases []string `json:"network_alias,omitempty"`
- CNINetworks []string `json:"cni_networks,omitempty"`
- UseImageResolvConf bool `json:"no_manage_resolv_conf,omitempty"`
- DNSOptions []string `json:"dns_option,omitempty"`
- DNSSearch []string `json:"dns_search,omitempty"`
- DNSServers []net.IP `json:"dns_server,omitempty"`
- Network specgen.Namespace `json:"netns,omitempty"`
- NoHosts bool `json:"no_manage_hosts,omitempty"`
- PublishPorts []types.PortMapping `json:"portmappings,omitempty"`
- StaticIP *net.IP `json:"static_ip,omitempty"`
- StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
+ AddHosts []string `json:"hostadd,omitempty"`
+ Aliases []string `json:"network_alias,omitempty"`
+ Networks map[string]types.PerNetworkOptions `json:"networks,omitempty"`
+ UseImageResolvConf bool `json:"no_manage_resolv_conf,omitempty"`
+ DNSOptions []string `json:"dns_option,omitempty"`
+ DNSSearch []string `json:"dns_search,omitempty"`
+ DNSServers []net.IP `json:"dns_server,omitempty"`
+ Network specgen.Namespace `json:"netns,omitempty"`
+ NoHosts bool `json:"no_manage_hosts,omitempty"`
+ PublishPorts []types.PortMapping `json:"portmappings,omitempty"`
// NetworkOptions are additional options for each network
NetworkOptions map[string][]string `json:"network_options,omitempty"`
}
diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go
index 269cd2d27..60a1efb22 100644
--- a/pkg/domain/filters/containers.go
+++ b/pkg/domain/filters/containers.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
- "github.com/containers/podman/v3/pkg/network"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
)
@@ -210,6 +209,15 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
return false
}, nil
case "network":
+ var inputNetNames []string
+ for _, val := range filterValues {
+ net, err := r.Network().NetworkInspect(val)
+ if err != nil {
+ // ignore not found errors
+ break
+ }
+ inputNetNames = append(inputNetNames, net.Name)
+ }
return func(c *libpod.Container) bool {
networkMode := c.NetworkMode()
// support docker like `--filter network=container:<IDorName>`
@@ -241,18 +249,14 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo
return false
}
- networks, _, err := c.Networks()
+ networks, err := c.Networks()
// if err or no networks, quick out
if err != nil || len(networks) == 0 {
return false
}
for _, net := range networks {
- netID := network.GetNetworkID(net)
- for _, val := range filterValues {
- // match by network name or id
- if val == net || val == netID {
- return true
- }
+ if util.StringInSlice(net, inputNetNames) {
+ return true
}
}
return false
diff --git a/pkg/domain/filters/pods.go b/pkg/domain/filters/pods.go
index 9a2f0a3ba..8231dbc79 100644
--- a/pkg/domain/filters/pods.go
+++ b/pkg/domain/filters/pods.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
- "github.com/containers/podman/v3/pkg/network"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
)
@@ -14,7 +13,7 @@ import (
// GeneratePodFilterFunc takes a filter and filtervalue (key, value)
// and generates a libpod function that can be used to filter
// pods
-func GeneratePodFilterFunc(filter string, filterValues []string) (
+func GeneratePodFilterFunc(filter string, filterValues []string, r *libpod.Runtime) (
func(pod *libpod.Pod) bool, error) {
switch filter {
case "ctr-ids":
@@ -128,24 +127,29 @@ func GeneratePodFilterFunc(filter string, filterValues []string) (
return false
}, nil
case "network":
+ var inputNetNames []string
+ for _, val := range filterValues {
+ net, err := r.Network().NetworkInspect(val)
+ if err != nil {
+ // ignore not found errors
+ break
+ }
+ inputNetNames = append(inputNetNames, net.Name)
+ }
return func(p *libpod.Pod) bool {
infra, err := p.InfraContainer()
// no infra, quick out
if err != nil {
return false
}
- networks, _, err := infra.Networks()
+ networks, err := infra.Networks()
// if err or no networks, quick out
if err != nil || len(networks) == 0 {
return false
}
for _, net := range networks {
- netID := network.GetNetworkID(net)
- for _, val := range filterValues {
- // match by network name or id
- if val == net || val == netID {
- return true
- }
+ if util.StringInSlice(net, inputNetNames) {
+ return true
}
}
return false
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index d1af4a479..bf4dcff62 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -927,6 +927,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
for _, w := range warn {
fmt.Fprintf(os.Stderr, "%s\n", w)
}
+
rtSpec, spec, optsN, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec)
if err != nil {
return nil, err
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/abi/network.go b/pkg/domain/infra/abi/network.go
index ee7403ed5..c7b12663c 100644
--- a/pkg/domain/infra/abi/network.go
+++ b/pkg/domain/infra/abi/network.go
@@ -71,7 +71,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
}
// We need to iterate containers looking to see if they belong to the given network
for _, c := range containers {
- networks, _, err := c.Networks()
+ networks, err := c.Networks()
// if container vanished or network does not exist, go to next container
if errors.Is(err, define.ErrNoSuchNetwork) || errors.Is(err, define.ErrNoSuchCtr) {
continue
@@ -124,7 +124,7 @@ func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname st
}
func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
- return ic.Libpod.ConnectContainerToNetwork(options.Container, networkname, options.Aliases)
+ return ic.Libpod.ConnectContainerToNetwork(options.Container, networkname, options.PerNetworkOptions)
}
// NetworkExists checks if the given network exists
@@ -152,7 +152,7 @@ func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.Ne
// containers want
networksToKeep := make(map[string]bool)
for _, c := range cons {
- nets, _, err := c.Networks()
+ nets, err := c.Networks()
if err != nil {
return nil, err
}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index 4c024a3d8..6b3b04a0b 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -6,7 +6,6 @@ import (
"fmt"
"io"
"io/ioutil"
- "net"
"os"
"path/filepath"
"strconv"
@@ -18,6 +17,7 @@ import (
"github.com/containers/image/v5/types"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
+ nettypes "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/autoupdate"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/specgen"
@@ -190,39 +190,52 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
}
}
- podOpt := entities.PodCreateOptions{Infra: true, Net: &entities.NetOptions{StaticIP: &net.IP{}, StaticMAC: &net.HardwareAddr{}, NoHosts: options.NoHosts}}
+ podOpt := entities.PodCreateOptions{Infra: true, Net: &entities.NetOptions{NoHosts: options.NoHosts}}
podOpt, err = kube.ToPodOpt(ctx, podName, podOpt, podYAML)
if err != nil {
return nil, err
}
- if options.Network != "" {
- ns, cniNets, netOpts, err := specgen.ParseNetworkString(options.Network)
- if err != nil {
- return nil, err
- }
-
- if (ns.IsBridge() && len(cniNets) == 0) || ns.IsHost() {
- return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
- }
+ ns, networks, netOpts, err := specgen.ParseNetworkFlag(options.Networks)
+ if err != nil {
+ return nil, err
+ }
- podOpt.Net.Network = ns
- if len(cniNets) > 0 {
- podOpt.Net.CNINetworks = append(podOpt.Net.CNINetworks, cniNets...)
- }
- if len(netOpts) > 0 {
- podOpt.Net.NetworkOptions = netOpts
- }
+ if (ns.IsBridge() && len(networks) == 0) || ns.IsHost() {
+ return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
}
+ podOpt.Net.Network = ns
+ podOpt.Net.Networks = networks
+ podOpt.Net.NetworkOptions = netOpts
+
+ // FIXME This is very hard to support properly with a good ux
if len(options.StaticIPs) > *ipIndex {
- podOpt.Net.StaticIP = &options.StaticIPs[*ipIndex]
+ if !podOpt.Net.Network.IsBridge() {
+ errors.Wrap(define.ErrInvalidArg, "static ip addresses can only be set when the network mode is bridge")
+ }
+ if len(podOpt.Net.Networks) != 1 {
+ return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static ip addresses for more than network, use netname:ip=<ip> syntax to specify ips for more than network")
+ }
+ for name, netOpts := range podOpt.Net.Networks {
+ netOpts.StaticIPs = append(netOpts.StaticIPs, options.StaticIPs[*ipIndex])
+ podOpt.Net.Networks[name] = netOpts
+ }
} else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one ip
logrus.Warn("No more static ips left using a random one")
}
if len(options.StaticMACs) > *ipIndex {
- podOpt.Net.StaticMAC = &options.StaticMACs[*ipIndex]
+ if !podOpt.Net.Network.IsBridge() {
+ errors.Wrap(define.ErrInvalidArg, "static mac address can only be set when the network mode is bridge")
+ }
+ if len(podOpt.Net.Networks) != 1 {
+ return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static mac address for more than network, use netname:mac=<mac> syntax to specify mac for more than network")
+ }
+ for name, netOpts := range podOpt.Net.Networks {
+ netOpts.StaticMAC = nettypes.HardwareAddr(options.StaticMACs[*ipIndex])
+ podOpt.Net.Networks[name] = netOpts
+ }
} else if len(options.StaticIPs) > 0 {
// only warn if the user has set at least one mac
logrus.Warn("No more static macs left using a random one")
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 028de9e81..7bda7e994 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -325,7 +325,7 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
filters := make([]libpod.PodFilter, 0, len(options.Filters))
for k, v := range options.Filters {
- f, err := dfilters.GeneratePodFilterFunc(k, v)
+ f, err := dfilters.GeneratePodFilterFunc(k, v, ic.Libpod)
if err != nil {
return nil, err
}
@@ -376,7 +376,7 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
if err != nil {
return nil, err
}
- networks, _, err = infra.Networks()
+ networks, err = infra.Networks()
if err != nil {
return nil, err
}
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/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go
index 069982d30..b5050345a 100644
--- a/pkg/domain/infra/tunnel/network.go
+++ b/pkg/domain/infra/tunnel/network.go
@@ -81,8 +81,7 @@ func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname st
// NetworkConnect removes a container from a given network
func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, opts entities.NetworkConnectOptions) error {
- options := new(network.ConnectOptions).WithAliases(opts.Aliases)
- return network.Connect(ic.ClientCtx, networkname, opts.Container, options)
+ return network.Connect(ic.ClientCtx, networkname, opts.Container, &opts.PerNetworkOptions)
}
// NetworkExists checks if the given network exists
diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go
index 75952ce2c..103be0cf1 100644
--- a/pkg/domain/infra/tunnel/play.go
+++ b/pkg/domain/infra/tunnel/play.go
@@ -11,7 +11,7 @@ import (
func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password)
options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps)
- options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Network).WithSeccompProfileRoot(opts.SeccompProfileRoot)
+ options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Networks).WithSeccompProfileRoot(opts.SeccompProfileRoot)
options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs)
if len(opts.LogOptions) > 0 {
options.WithLogOptions(opts.LogOptions)
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 55d5dd7b4..4f2947ac0 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -21,8 +21,18 @@ type InitOptions struct {
IsDefault bool
Memory uint64
Name string
+ TimeZone string
URI url.URL
Username string
+ ReExec bool
+}
+
+type Provider interface {
+ NewMachine(opts InitOptions) (VM, error)
+ LoadVMByName(name string) (VM, error)
+ List(opts ListOptions) ([]*ListResponse, error)
+ IsValidVMName(name string) (bool, error)
+ CheckExclusiveActiveVM() (bool, string, error)
}
type RemoteConnectionType string
@@ -48,6 +58,7 @@ type Download struct {
Sha256sum string
URL *url.URL
VMName string
+ Size int64
}
type ListOptions struct{}
@@ -80,7 +91,7 @@ type RemoveOptions struct {
}
type VM interface {
- Init(opts InitOptions) error
+ Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
@@ -88,7 +99,7 @@ type VM interface {
}
type DistributionDownload interface {
- DownloadImage() error
+ HasUsableCache() (bool, error)
Get() *Download
}
diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go
index ed1093264..d28ffcef1 100644
--- a/pkg/machine/connection.go
+++ b/pkg/machine/connection.go
@@ -1,4 +1,5 @@
-// +build amd64,!windows arm64,!windows
+//go:build amd64 || arm64
+// +build amd64 arm64
package machine
diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go
index 99197ac0e..60ab471ee 100644
--- a/pkg/machine/fcos.go
+++ b/pkg/machine/fcos.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -65,25 +65,6 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload
return fcd, nil
}
-func (f FcosDownload) getLocalUncompressedName() string {
- uncompressedFilename := filepath.Join(filepath.Dir(f.LocalPath), f.VMName+"_"+f.ImageName)
- return strings.TrimSuffix(uncompressedFilename, ".xz")
-}
-
-func (f FcosDownload) DownloadImage() error {
- // check if the latest image is already present
- ok, err := UpdateAvailable(&f.Download)
- if err != nil {
- return err
- }
- if !ok {
- if err := DownloadVMImage(f.URL, f.LocalPath); err != nil {
- return err
- }
- }
- return Decompress(f.LocalPath, f.getLocalUncompressedName())
-}
-
func (f FcosDownload) Get() *Download {
return &f.Download
}
@@ -95,14 +76,14 @@ type fcosDownloadInfo struct {
Sha256Sum string
}
-func UpdateAvailable(d *Download) (bool, error) {
+func (f FcosDownload) HasUsableCache() (bool, error) {
// check the sha of the local image if it exists
// get the sha of the remote image
// == dont bother to pull
- if _, err := os.Stat(d.LocalPath); os.IsNotExist(err) {
+ if _, err := os.Stat(f.LocalPath); os.IsNotExist(err) {
return false, nil
}
- fd, err := os.Open(d.LocalPath)
+ fd, err := os.Open(f.LocalPath)
if err != nil {
return false, err
}
@@ -115,7 +96,7 @@ func UpdateAvailable(d *Download) (bool, error) {
if err != nil {
return false, err
}
- return sum.Encoded() == d.Sha256sum, nil
+ return sum.Encoded() == f.Sha256sum, nil
}
func getFcosArch() string {
diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go
new file mode 100644
index 000000000..cd713dde7
--- /dev/null
+++ b/pkg/machine/fedora.go
@@ -0,0 +1,122 @@
+// +build amd64 arm64
+
+package machine
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ githubURL = "http://github.com/fedora-cloud/docker-brew-fedora/"
+)
+
+type FedoraDownload struct {
+ Download
+}
+
+func NewFedoraDownloader(vmType, vmName, releaseStream string) (DistributionDownload, error) {
+ imageName, downloadURL, size, err := getFedoraDownload(releaseStream)
+ if err != nil {
+ return nil, err
+ }
+
+ dataDir, err := GetDataDir(vmType)
+ if err != nil {
+ return nil, err
+ }
+
+ f := FedoraDownload{
+ Download: Download{
+ Arch: getFcosArch(),
+ Artifact: artifact,
+ Format: Format,
+ ImageName: imageName,
+ LocalPath: filepath.Join(dataDir, imageName),
+ URL: downloadURL,
+ VMName: vmName,
+ Size: size,
+ },
+ }
+ f.Download.LocalUncompressedFile = f.getLocalUncompressedName()
+ return f, nil
+}
+
+func (f FedoraDownload) Get() *Download {
+ return &f.Download
+}
+
+func (f FedoraDownload) HasUsableCache() (bool, error) {
+ info, err := os.Stat(f.LocalPath)
+ if err != nil {
+ return false, nil
+ }
+ return info.Size() == f.Size, nil
+}
+
+func truncRead(url string) ([]byte, error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+
+ defer func() {
+ if err := resp.Body.Close(); err != nil {
+ logrus.Error(err)
+ }
+ }()
+
+ body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
+ if err != nil {
+ return nil, err
+ }
+
+ _, _ = io.Copy(io.Discard, resp.Body)
+
+ return body, nil
+}
+
+func getFedoraDownload(releaseStream string) (string, *url.URL, int64, error) {
+ dirURL := githubURL + "tree/" + releaseStream + "/" + getFcosArch() + "/"
+ body, err := truncRead(dirURL)
+ if err != nil {
+ return "", nil, -1, err
+ }
+
+ rx, err := regexp.Compile(`fedora[^\"]+xz`)
+ if err != nil {
+ return "", nil, -1, err
+ }
+ file := rx.FindString(string(body))
+ if len(file) <= 0 {
+ return "", nil, -1, fmt.Errorf("could not locate Fedora download at %s", dirURL)
+ }
+
+ rawURL := githubURL + "raw/" + releaseStream + "/" + getFcosArch() + "/"
+ newLocation := rawURL + file
+ downloadURL, err := url.Parse(newLocation)
+ if err != nil {
+ return "", nil, -1, errors.Wrapf(err, "invalid URL generated from discovered Fedora file: %s", newLocation)
+ }
+
+ resp, err := http.Head(newLocation)
+ if err != nil {
+ return "", nil, -1, errors.Wrapf(err, "head request failed: %s", newLocation)
+ }
+ _ = resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return "", nil, -1, fmt.Errorf("head request failed [%d] on download: %s", resp.StatusCode, newLocation)
+ }
+
+ return file, downloadURL, resp.ContentLength, nil
+}
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 5c465d37d..84d3be296 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
@@ -7,6 +7,10 @@ import (
"fmt"
"io/ioutil"
"net/url"
+ "os"
+ "path/filepath"
+
+ "github.com/sirupsen/logrus"
)
/*
@@ -44,6 +48,7 @@ func getNodeGrp(grpName string) NodeGroup {
type DynamicIgnition struct {
Name string
Key string
+ TimeZone string
VMName string
WritePath string
}
@@ -76,6 +81,37 @@ func NewIgnitionFile(ign DynamicIgnition) error {
Links: getLinks(ign.Name),
}
+ // Add or set the time zone for the machine
+ if len(ign.TimeZone) > 0 {
+ var (
+ err error
+ tz string
+ )
+ // local means the same as the host
+ // lookup where it is pointing to on the host
+ if ign.TimeZone == "local" {
+ tz, err = getLocalTimeZone()
+ if err != nil {
+ return err
+ }
+ } else {
+ tz = ign.TimeZone
+ }
+ tzLink := Link{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/localtime",
+ Overwrite: boolToPtr(false),
+ User: getNodeUsr("root"),
+ },
+ LinkEmbedded1: LinkEmbedded1{
+ Hard: boolToPtr(false),
+ Target: filepath.Join("/usr/share/zoneinfo", tz),
+ },
+ }
+ ignStorage.Links = append(ignStorage.Links, tzLink)
+ }
+
// ready is a unit file that sets up the virtual serial device
// where when the VM is done configuring, it will send an ack
// so a listening host knows it can being interacting with it
@@ -322,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/machine/ignition_darwin.go b/pkg/machine/ignition_darwin.go
new file mode 100644
index 000000000..9ede4b026
--- /dev/null
+++ b/pkg/machine/ignition_darwin.go
@@ -0,0 +1,16 @@
+//+build darwin
+
+package machine
+
+import (
+ "os"
+ "strings"
+)
+
+func getLocalTimeZone() (string, error) {
+ tzPath, err := os.Readlink("/etc/localtime")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimPrefix(tzPath, "/var/db/timezone/zoneinfo"), nil
+}
diff --git a/pkg/machine/ignition_linux.go b/pkg/machine/ignition_linux.go
new file mode 100644
index 000000000..6db5a8e7a
--- /dev/null
+++ b/pkg/machine/ignition_linux.go
@@ -0,0 +1,15 @@
+package machine
+
+import (
+ "os/exec"
+ "strings"
+)
+
+func getLocalTimeZone() (string, error) {
+ output, err := exec.Command("timedatectl", "show", "--property=Timezone").Output()
+ if err != nil {
+ return "", err
+ }
+ // Remove prepended field and the newline
+ return strings.TrimPrefix(strings.TrimSuffix(string(output), "\n"), "Timezone="), nil
+}
diff --git a/pkg/machine/ignition_schema.go b/pkg/machine/ignition_schema.go
index aa4b8e060..8cfb0d04e 100644
--- a/pkg/machine/ignition_schema.go
+++ b/pkg/machine/ignition_schema.go
@@ -1,4 +1,4 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
diff --git a/pkg/machine/ignition_windows.go b/pkg/machine/ignition_windows.go
new file mode 100644
index 000000000..c0de48bd3
--- /dev/null
+++ b/pkg/machine/ignition_windows.go
@@ -0,0 +1,7 @@
+//+build windows
+
+package machine
+
+func getLocalTimeZone() (string, error) {
+ return "", nil
+}
diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go
index 319fc2b4e..711b091f0 100644
--- a/pkg/machine/keys.go
+++ b/pkg/machine/keys.go
@@ -1,13 +1,21 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
import (
+ "errors"
+ "fmt"
"io/ioutil"
+ "os"
"os/exec"
+ "path/filepath"
"strings"
+
+ "github.com/sirupsen/logrus"
)
+var sshCommand = []string{"ssh-keygen", "-N", "", "-t", "ed25519", "-f"}
+
// CreateSSHKeys makes a priv and pub ssh key for interacting
// the a VM.
func CreateSSHKeys(writeLocation string) (string, error) {
@@ -21,7 +29,42 @@ func CreateSSHKeys(writeLocation string) (string, error) {
return strings.TrimSuffix(string(b), "\n"), nil
}
+func CreateSSHKeysPrefix(dir string, file string, passThru bool, skipExisting bool, prefix ...string) (string, error) {
+ location := filepath.Join(dir, file)
+
+ _, e := os.Stat(location)
+ if !skipExisting || errors.Is(e, os.ErrNotExist) {
+ if err := generatekeysPrefix(dir, file, passThru, prefix...); err != nil {
+ return "", err
+ }
+ } else {
+ fmt.Println("Keys already exist, reusing")
+ }
+ b, err := ioutil.ReadFile(filepath.Join(dir, file) + ".pub")
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSuffix(string(b), "\n"), nil
+}
+
// generatekeys creates an ed25519 set of keys
func generatekeys(writeLocation string) error {
- return exec.Command("ssh-keygen", "-N", "", "-t", "ed25519", "-f", writeLocation).Run()
+ args := append(append([]string{}, sshCommand[1:]...), writeLocation)
+ return exec.Command(sshCommand[0], args...).Run()
+}
+
+// generatekeys creates an ed25519 set of keys
+func generatekeysPrefix(dir string, file string, passThru bool, prefix ...string) error {
+ args := append([]string{}, prefix[1:]...)
+ args = append(args, sshCommand...)
+ args = append(args, file)
+ cmd := exec.Command(prefix[0], args...)
+ cmd.Dir = dir
+ if passThru {
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ }
+ logrus.Debugf("Running wsl cmd %v in dir: %s", args, dir)
+ return cmd.Run()
}
diff --git a/pkg/machine/machine_unsupported.go b/pkg/machine/machine_unsupported.go
index 9309d16bc..da1437984 100644
--- a/pkg/machine/machine_unsupported.go
+++ b/pkg/machine/machine_unsupported.go
@@ -1,3 +1,3 @@
-// +build !amd64 amd64,windows
+// +build !amd64,!arm64
package machine
diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go
index 3c8422a30..280b47f96 100644
--- a/pkg/machine/pull.go
+++ b/pkg/machine/pull.go
@@ -1,8 +1,9 @@
-// +build amd64,!windows arm64,!windows
+// +build amd64 arm64
package machine
import (
+ "bufio"
"fmt"
"io"
"io/ioutil"
@@ -17,6 +18,7 @@ import (
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/storage/pkg/archive"
"github.com/sirupsen/logrus"
+ "github.com/ulikunitz/xz"
"github.com/vbauerster/mpb/v6"
"github.com/vbauerster/mpb/v6/decor"
)
@@ -43,7 +45,7 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return nil, err
}
if len(getURL.Scheme) > 0 {
- urlSplit := strings.Split(pullPath, "/")
+ urlSplit := strings.Split(getURL.Path, "/")
imageName = urlSplit[len(urlSplit)-1]
dl.LocalUncompressedFile = filepath.Join(dataDir, imageName)
dl.URL = getURL
@@ -63,39 +65,48 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload
return gd, nil
}
-func (g GenericDownload) getLocalUncompressedName() string {
+func (d Download) getLocalUncompressedName() string {
var (
extension string
)
switch {
- case strings.HasSuffix(g.LocalPath, ".bz2"):
+ case strings.HasSuffix(d.LocalPath, ".bz2"):
extension = ".bz2"
- case strings.HasSuffix(g.LocalPath, ".gz"):
+ case strings.HasSuffix(d.LocalPath, ".gz"):
extension = ".gz"
- case strings.HasSuffix(g.LocalPath, ".xz"):
+ case strings.HasSuffix(d.LocalPath, ".xz"):
extension = ".xz"
}
- uncompressedFilename := filepath.Join(filepath.Dir(g.LocalUncompressedFile), g.VMName+"_"+g.ImageName)
+ uncompressedFilename := filepath.Join(filepath.Dir(d.LocalPath), d.VMName+"_"+d.ImageName)
return strings.TrimSuffix(uncompressedFilename, extension)
}
-func (g GenericDownload) DownloadImage() error {
+func (g GenericDownload) Get() *Download {
+ return &g.Download
+}
+
+func (g GenericDownload) HasUsableCache() (bool, error) {
// If we have a URL for this "downloader", we now pull it
- if g.URL != nil {
- if err := DownloadVMImage(g.URL, g.LocalPath); err != nil {
+ return g.URL == nil, nil
+}
+
+func DownloadImage(d DistributionDownload) error {
+ // check if the latest image is already present
+ ok, err := d.HasUsableCache()
+ if err != nil {
+ return err
+ }
+ if !ok {
+ if err := DownloadVMImage(d.Get().URL, d.Get().LocalPath); err != nil {
return err
}
}
- return Decompress(g.LocalPath, g.getLocalUncompressedName())
-}
-
-func (g GenericDownload) Get() *Download {
- return &g.Download
+ return Decompress(d.Get().LocalPath, d.Get().getLocalUncompressedName())
}
// DownloadVMImage downloads a VM image from url to given path
// with download status
-func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
+func DownloadVMImage(downloadURL *url2.URL, localImagePath string) error {
out, err := os.Create(localImagePath)
if err != nil {
return err
@@ -120,7 +131,7 @@ func DownloadVMImage(downloadURL fmt.Stringer, localImagePath string) error {
return fmt.Errorf("error downloading VM image %s: %s", downloadURL, resp.Status)
}
size := resp.ContentLength
- urlSplit := strings.Split(downloadURL.String(), "/")
+ urlSplit := strings.Split(downloadURL.Path, "/")
prefix := "Downloading VM image: " + urlSplit[len(urlSplit)-1]
onComplete := prefix + ": done"
@@ -177,24 +188,50 @@ func Decompress(localPath, uncompressedPath string) error {
// Will error out if file without .xz already exists
// Maybe extracting then renameing is a good idea here..
// depends on xz: not pre-installed on mac, so it becomes a brew dependency
-func decompressXZ(src string, output io.Writer) error {
- cmd := exec.Command("xzcat", "-k", src)
- //cmd := exec.Command("xz", "-d", "-k", "-v", src)
- stdOut, err := cmd.StdoutPipe()
- if err != nil {
- return err
+func decompressXZ(src string, output io.WriteCloser) error {
+ var read io.Reader
+ var cmd *exec.Cmd
+ // Prefer xz utils for fastest performance, fallback to go xi2 impl
+ if _, err := exec.LookPath("xzcat"); err == nil {
+ cmd = exec.Command("xzcat", "-k", src)
+ read, err = cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+ cmd.Stderr = os.Stderr
+ } else {
+ file, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ // This XZ implementation is reliant on buffering. It is also 3x+ slower than XZ utils.
+ // Consider replacing with a faster implementation (e.g. xi2) if podman machine is
+ // updated with a larger image for the distribution base.
+ buf := bufio.NewReader(file)
+ read, err = xz.NewReader(buf)
+ if err != nil {
+ return err
+ }
}
- //cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
+
+ done := make(chan bool)
go func() {
- if _, err := io.Copy(output, stdOut); err != nil {
+ if _, err := io.Copy(output, read); err != nil {
logrus.Error(err)
}
+ output.Close()
+ done <- true
}()
- return cmd.Run()
+
+ if cmd != nil {
+ return cmd.Run()
+ }
+ <-done
+ return nil
}
-func decompressEverythingElse(src string, output io.Writer) error {
+func decompressEverythingElse(src string, output io.WriteCloser) error {
f, err := os.Open(src)
if err != nil {
return err
@@ -207,6 +244,9 @@ func decompressEverythingElse(src string, output io.Writer) error {
if err := uncompressStream.Close(); err != nil {
logrus.Error(err)
}
+ if err := output.Close(); err != nil {
+ logrus.Error(err)
+ }
}()
_, err = io.Copy(output, uncompressStream)
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index c04773450..8404079a2 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -4,6 +4,8 @@ package qemu
import "time"
+type Provider struct{}
+
type MachineVM struct {
// CPUs to be assigned to the VM
CPUs uint64
@@ -44,6 +46,4 @@ var (
// defaultQMPTimeout is the timeout duration for the
// qmp monitor interactions
defaultQMPTimeout time.Duration = 2 * time.Second
- // defaultRemoteUser describes the ssh username default
- defaultRemoteUser = "core"
)
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index 57c32bf74..a80a11573 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -21,18 +21,24 @@ import (
"github.com/containers/podman/v3/utils"
"github.com/containers/storage/pkg/homedir"
"github.com/digitalocean/go-qemu/qmp"
+ "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
+ qemuProvider = &Provider{}
// vmtype refers to qemu (vs libvirt, krun, etc)
vmtype = "qemu"
)
+func GetQemuProvider() machine.Provider {
+ return qemuProvider
+}
+
// NewMachine initializes an instance of a virtual machine based on the qemu
// virtualization.
-func NewMachine(opts machine.InitOptions) (machine.VM, error) {
+func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return nil, err
@@ -44,16 +50,8 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
ignitionFile := filepath.Join(vmConfigDir, vm.Name+".ign")
vm.IgnitionFilePath = ignitionFile
- // An image was specified
- if len(opts.ImagePath) > 0 {
- vm.ImagePath = opts.ImagePath
- }
-
- // Assign remote user name. if not provided, use default
+ vm.ImagePath = opts.ImagePath
vm.RemoteUsername = opts.Username
- if len(vm.RemoteUsername) < 1 {
- vm.RemoteUsername = defaultRemoteUser
- }
// Add a random port for ssh
port, err := utils.GetRandomPort()
@@ -106,7 +104,7 @@ func NewMachine(opts machine.InitOptions) (machine.VM, error) {
// LoadByName reads a json file that describes a known qemu vm
// and returns a vm instance
-func LoadVMByName(name string) (machine.VM, error) {
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
vm := new(MachineVM)
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
@@ -126,7 +124,7 @@ func LoadVMByName(name string) (machine.VM, error) {
// Init writes the json configuration file to the filesystem for
// other verbs (start, stop)
-func (v *MachineVM) Init(opts machine.InitOptions) error {
+func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
var (
key string
)
@@ -135,7 +133,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// its existence
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
- return err
+ return false, err
}
jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name)
@@ -145,12 +143,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// Get image as usual
v.ImageStream = opts.ImagePath
dd, err := machine.NewFcosDownloader(vmtype, v.Name, opts.ImagePath)
+
if err != nil {
- return err
+ return false, err
}
v.ImagePath = dd.Get().LocalUncompressedFile
- if err := dd.DownloadImage(); err != nil {
- return err
+ if err := machine.DownloadImage(dd); err != nil {
+ return false, err
}
default:
// The user has provided an alternate image which can be a file path
@@ -158,11 +157,11 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
v.ImageStream = "custom"
g, err := machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
if err != nil {
- return err
+ return false, err
}
v.ImagePath = g.Get().LocalUncompressedFile
- if err := g.DownloadImage(); err != nil {
- return err
+ if err := machine.DownloadImage(g); err != nil {
+ return false, err
}
}
// Add arch specific options including image location
@@ -174,12 +173,12 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 {
uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
if err := machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return err
+ return false, err
}
uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
- return err
+ return false, err
}
} else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
@@ -187,10 +186,10 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
// Write the JSON file
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
- return err
+ return false, err
}
if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
- return err
+ return false, err
}
// User has provided ignition file so keygen
@@ -198,17 +197,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) < 1 {
key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil {
- return err
+ return false, err
}
}
// Run arch specific things that need to be done
if err := v.prepare(); err != nil {
- return err
+ return false, err
}
originalDiskSize, err := getDiskSize(v.ImagePath)
if err != nil {
- return err
+ return false, err
}
// Resize the disk image to input disk size
// only if the virtualdisk size is less than
@@ -218,7 +217,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
resize.Stdout = os.Stdout
resize.Stderr = os.Stderr
if err := resize.Run(); err != nil {
- return errors.Errorf("error resizing image: %q", err)
+ return false, errors.Errorf("error resizing image: %q", err)
}
}
// If the user provides an ignition file, we need to
@@ -226,18 +225,20 @@ func (v *MachineVM) Init(opts machine.InitOptions) error {
if len(opts.IgnitionPath) > 0 {
inputIgnition, err := ioutil.ReadFile(opts.IgnitionPath)
if err != nil {
- return err
+ return false, err
}
- return ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
+ return false, ioutil.WriteFile(v.IgnitionFilePath, inputIgnition, 0644)
}
// Write the ignition file
ign := machine.DynamicIgnition{
Name: opts.Username,
Key: key,
VMName: v.Name,
+ TimeZone: opts.TimeZone,
WritePath: v.IgnitionFilePath,
}
- return machine.NewIgnitionFile(ign)
+ err = machine.NewIgnitionFile(ign)
+ return err == nil, err
}
// Start executes the qemu command line and forks it
@@ -569,7 +570,7 @@ func getDiskSize(path string) (uint64, error) {
}
// List lists all vm's that use qemu virtualization
-func List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
+func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
return GetVMInfos()
}
@@ -599,8 +600,8 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
listEntry.Stream = vm.ImageStream
listEntry.VMType = "qemu"
listEntry.CPUs = vm.CPUs
- listEntry.Memory = vm.Memory
- listEntry.DiskSize = vm.DiskSize
+ listEntry.Memory = vm.Memory * units.MiB
+ listEntry.DiskSize = vm.DiskSize * units.GiB
fi, err := os.Stat(fullPath)
if err != nil {
return err
@@ -625,7 +626,7 @@ func GetVMInfos() ([]*machine.ListResponse, error) {
return listed, err
}
-func IsValidVMName(name string) (bool, error) {
+func (p *Provider) IsValidVMName(name string) (bool, error) {
infos, err := GetVMInfos()
if err != nil {
return false, err
@@ -638,8 +639,9 @@ func IsValidVMName(name string) (bool, error) {
return false, nil
}
-// CheckActiveVM checks if there is a VM already running
-func CheckActiveVM() (bool, string, error) {
+// CheckExclusiveActiveVM checks if there is a VM already running
+// that does not allow other VMs to be running
+func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
vms, err := GetVMInfos()
if err != nil {
return false, "", errors.Wrap(err, "error checking VM active")
diff --git a/pkg/machine/qemu/machine_unsupported.go b/pkg/machine/qemu/machine_unsupported.go
index da06ac324..e3ce05e3d 100644
--- a/pkg/machine/qemu/machine_unsupported.go
+++ b/pkg/machine/qemu/machine_unsupported.go
@@ -1,3 +1,3 @@
-// +build !amd64 amd64,windows
+// +build !amd64,!arm64 windows
package qemu
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
new file mode 100644
index 000000000..b4ee79acc
--- /dev/null
+++ b/pkg/machine/wsl/machine.go
@@ -0,0 +1,1119 @@
+//go:build windows
+// +build windows
+
+package wsl
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/containers/podman/v3/pkg/machine"
+ "github.com/containers/podman/v3/utils"
+ "github.com/containers/storage/pkg/homedir"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/text/encoding/unicode"
+ "golang.org/x/text/transform"
+)
+
+var (
+ wslProvider = &Provider{}
+ // vmtype refers to qemu (vs libvirt, krun, etc)
+ vmtype = "wsl"
+)
+
+const (
+ ErrorSuccessRebootInitiated = 1641
+ ErrorSuccessRebootRequired = 3010
+)
+
+// Usermode networking avoids potential nftables compatibility issues between the distro
+// and the WSL Kernel. Additionally it avoids fw rule conflicts between distros, since
+// all instances run under the same Kernel at runtime
+const containersConf = `[containers]
+
+[engine]
+cgroup_manager = "cgroupfs"
+events_logger = "file"
+`
+
+const appendPort = `grep -q Port\ %d /etc/ssh/sshd_config || echo Port %d >> /etc/ssh/sshd_config`
+
+const configServices = `ln -fs /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/sshd.service
+ln -fs /usr/lib/systemd/system/podman.socket /etc/systemd/system/sockets.target.wants/podman.socket
+rm -f /etc/systemd/system/getty.target.wants/console-getty.service
+rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service
+rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service
+rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service
+ln -fs /dev/null /etc/systemd/system/console-getty.service
+mkdir -p /etc/systemd/system/systemd-sysusers.service.d/
+adduser -m [USER] -G wheel
+mkdir -p /home/[USER]/.config/systemd/[USER]/
+chown [USER]:[USER] /home/[USER]/.config
+`
+
+const sudoers = `%wheel ALL=(ALL) NOPASSWD: ALL
+`
+
+const bootstrap = `#!/bin/bash
+ps -ef | grep -v grep | grep -q systemd && exit 0
+nohup unshare --kill-child --fork --pid --mount --mount-proc --propagation shared /lib/systemd/systemd >/dev/null 2>&1 &
+sleep 0.1
+`
+
+const wslmotd = `
+You will be automatically entered into a nested process namespace where
+systemd is running. If you need to access the parent namespace, hit ctrl-d
+or type exit. This also means to log out you need to exit twice.
+
+`
+
+const sysdpid = "SYSDPID=`ps -eo cmd,pid | grep -m 1 ^/lib/systemd/systemd | awk '{print $2}'`"
+
+const profile = sysdpid + `
+if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
+ cat /etc/wslmotd
+ /usr/local/bin/enterns
+fi
+`
+
+const enterns = "#!/bin/bash\n" + sysdpid + `
+if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then
+ nsenter -m -p -t $SYSDPID "$@"
+fi
+`
+
+const waitTerm = sysdpid + `
+if [ ! -z "$SYSDPID" ]; then
+ timeout 60 tail -f /dev/null --pid $SYSDPID
+fi
+`
+
+// WSL kernel does not have sg and crypto_user modules
+const overrideSysusers = `[Service]
+LoadCredential=
+`
+
+const lingerService = `[Unit]
+Description=A systemd user unit demo
+After=network-online.target
+Wants=network-online.target podman.socket
+[Service]
+ExecStart=/usr/bin/sleep infinity
+`
+
+const lingerSetup = `mkdir -p /home/[USER]/.config/systemd/[USER]/default.target.wants
+ln -fs /home/[USER]/.config/systemd/[USER]/linger-example.service \
+ /home/[USER]/.config/systemd/[USER]/default.target.wants/linger-example.service
+`
+
+const wslInstallError = `Could not %s. See previous output for any potential failure details.
+If you can not resolve the issue, and rerunning fails, try the "wsl --install" process
+outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install
+
+`
+
+const wslKernelError = `Could not %s. See previous output for any potential failure details.
+If you can not resolve the issue, try rerunning the "podman machine init command". If that fails
+try the "wsl --update" command and then rerun "podman machine init". Finally, if all else fails,
+try following the steps outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install
+
+`
+
+const wslInstallKernel = "install the WSL Kernel"
+
+const wslOldVersion = `Automatic installation of WSL can not be performed on this version of Windows
+Either update to Build 19041 (or later), or perform the manual installation steps
+outlined in the following article:
+
+http://docs.microsoft.com/en-us/windows/wsl/install\
+
+`
+
+type Provider struct{}
+
+type MachineVM struct {
+ // IdentityPath is the fq path to the ssh priv key
+ IdentityPath string
+ // IgnitionFilePath is the fq path to the .ign file
+ ImageStream string
+ // ImagePath is the fq path to
+ ImagePath string
+ // Name of the vm
+ Name string
+ // SSH port for user networking
+ Port int
+ // RemoteUsername of the vm user
+ RemoteUsername string
+}
+
+type ExitCodeError struct {
+ code uint
+}
+
+func (e *ExitCodeError) Error() string {
+ return fmt.Sprintf("Process failed with exit code: %d", e.code)
+}
+
+func GetWSLProvider() machine.Provider {
+ return wslProvider
+}
+
+// NewMachine initializes an instance of a virtual machine based on the qemu
+// virtualization.
+func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) {
+ vm := new(MachineVM)
+ if len(opts.Name) > 0 {
+ vm.Name = opts.Name
+ }
+
+ vm.ImagePath = opts.ImagePath
+ vm.RemoteUsername = opts.Username
+
+ // Add a random port for ssh
+ port, err := utils.GetRandomPort()
+ if err != nil {
+ return nil, err
+ }
+ vm.Port = port
+
+ return vm, nil
+}
+
+// LoadByName reads a json file that describes a known qemu vm
+// and returns a vm instance
+func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
+ vm := new(MachineVM)
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+ b, err := ioutil.ReadFile(filepath.Join(vmConfigDir, name+".json"))
+ if os.IsNotExist(err) {
+ return nil, errors.Wrap(machine.ErrNoSuchVM, name)
+ }
+ if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(b, vm)
+ return vm, err
+}
+
+// Init writes the json configuration file to the filesystem for
+// other verbs (start, stop)
+func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
+ if cont, err := checkAndInstallWSL(opts); !cont {
+ appendOutputIfError(opts.ReExec, err)
+ return cont, err
+ }
+
+ homeDir := homedir.Get()
+ sshDir := filepath.Join(homeDir, ".ssh")
+ v.IdentityPath = filepath.Join(sshDir, v.Name)
+
+ if err := downloadDistro(v, opts); err != nil {
+ return false, err
+ }
+
+ if err := writeJSON(v); err != nil {
+ return false, err
+ }
+
+ if err := setupConnections(v, opts, sshDir); err != nil {
+ return false, err
+ }
+
+ dist, err := provisionWSLDist(v)
+ if err != nil {
+ return false, err
+ }
+
+ fmt.Println("Configuring system...")
+ if err = configureSystem(v, dist); err != nil {
+ return false, err
+ }
+
+ if err = installScripts(dist); err != nil {
+ return false, err
+ }
+
+ if err = createKeys(v, dist, sshDir); err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
+
+func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
+ var (
+ dd machine.DistributionDownload
+ err error
+ )
+
+ if _, e := strconv.Atoi(opts.ImagePath); e == nil {
+ v.ImageStream = opts.ImagePath
+ dd, err = machine.NewFedoraDownloader(vmtype, v.Name, v.ImageStream)
+ } else {
+ v.ImageStream = "custom"
+ dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath)
+ }
+ if err != nil {
+ return err
+ }
+
+ v.ImagePath = dd.Get().LocalUncompressedFile
+ return machine.DownloadImage(dd)
+}
+
+func writeJSON(v *MachineVM) error {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ return errors.Wrap(err, "could not write machine json config")
+ }
+
+ return nil
+}
+
+func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
+ uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
+ if err := machine.AddConnection(&uriRoot, v.Name+"-root", filepath.Join(sshDir, v.Name), opts.IsDefault); err != nil {
+ return err
+ }
+
+ user := opts.Username
+ uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", withUser("/run/[USER]/1000/podman/podman.sock", user), strconv.Itoa(v.Port), v.RemoteUsername)
+ return machine.AddConnection(&uri, v.Name, filepath.Join(sshDir, v.Name), opts.IsDefault)
+}
+
+func provisionWSLDist(v *MachineVM) (string, error) {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", err
+ }
+
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ distTarget := filepath.Join(distDir, v.Name)
+ if err := os.MkdirAll(distDir, 0755); err != nil {
+ return "", errors.Wrap(err, "could not create wsldist directory")
+ }
+
+ dist := toDist(v.Name)
+ fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...")
+ if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil {
+ return "", errors.Wrap(err, "WSL import of guest OS failed")
+ }
+
+ fmt.Println("Installing packages (this will take awhile)...")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil {
+ return "", errors.Wrap(err, "package upgrade on guest OS failed")
+ }
+
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install",
+ "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil {
+ return "", errors.Wrap(err, "package installation on guest OS failed")
+ }
+
+ // Fixes newuidmap
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil {
+ return "", errors.Wrap(err, "package reinstallation of shadow-utils on guest OS failed")
+ }
+
+ // Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every
+ // operation when mount was not present on the initial start. Force a cycle so that it won't
+ // repeatedly complain.
+ if winVersionAtLeast(10, 0, 22000) {
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ logrus.Warnf("could not cycle WSL dist: %s", err.Error())
+ }
+ }
+
+ return dist, nil
+}
+
+func createKeys(v *MachineVM, dist string, sshDir string) error {
+ user := v.RemoteUsername
+
+ if err := os.MkdirAll(sshDir, 0700); err != nil {
+ return errors.Wrap(err, "could not create ssh directory")
+ }
+
+ if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil {
+ return errors.Wrap(err, "could not cycle WSL dist")
+ }
+
+ key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist)
+ if err != nil {
+ return errors.Wrap(err, "could not create ssh keys")
+ }
+
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+
+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil {
+ return errors.Wrap(err, "could not create root authorized keys on guest OS")
+ }
+
+ userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+
+ "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+
+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user)
+ if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil {
+ return errors.Wrapf(err, "could not create '%s' authorized keys on guest OS", v.RemoteUsername)
+ }
+
+ return nil
+}
+
+func configureSystem(v *MachineVM, dist string) error {
+ user := v.RemoteUsername
+ if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil {
+ return errors.Wrap(err, "could not configure SSH port for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settings for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil {
+ return errors.Wrap(err, "could not add wheel to sudoers")
+ }
+
+ if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c",
+ "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil {
+ return errors.Wrap(err, "could not generate systemd-sysusers override for guest OS")
+ }
+
+ lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user)
+ if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil {
+ return errors.Wrap(err, "could not generate linger service for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil {
+ return errors.Wrap(err, "could not configure systemd settomgs for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil {
+ return errors.Wrap(err, "could not create containers.conf for guest OS")
+ }
+
+ return nil
+}
+
+func installScripts(dist string) error {
+ if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c",
+ "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil {
+ return errors.Wrap(err, "could not create enterns script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c",
+ "cat > /etc/profile.d/enterns.sh"); err != nil {
+ return errors.Wrap(err, "could not create motd profile script for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil {
+ return errors.Wrap(err, "could not create a WSL MOTD for guest OS")
+ }
+
+ if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c",
+ "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil {
+ return errors.Wrap(err, "could not create bootstrap script for guest OS")
+ }
+
+ return nil
+}
+
+func checkAndInstallWSL(opts machine.InitOptions) (bool, error) {
+ if isWSLInstalled() {
+ return true, nil
+ }
+
+ admin := hasAdminRights()
+
+ if !isWSLFeatureEnabled() {
+ return false, attemptFeatureInstall(opts, admin)
+ }
+
+ skip := false
+ if !opts.ReExec && !admin {
+ fmt.Println("Launching WSL Kernel Install...")
+ if err := launchElevate(wslInstallKernel); err != nil {
+ return false, err
+ }
+
+ skip = true
+ }
+
+ if !skip {
+ if err := installWslKernel(); err != nil {
+ fmt.Fprintf(os.Stderr, wslKernelError, wslInstallKernel)
+ return false, err
+ }
+
+ if opts.ReExec {
+ return false, nil
+ }
+ }
+
+ return true, nil
+}
+
+func attemptFeatureInstall(opts machine.InitOptions, admin bool) error {
+ if !winVersionAtLeast(10, 0, 18362) {
+ return errors.Errorf("Your version of Windows does not support WSL. Update to Windows 10 Build 19041 or later")
+ } else if !winVersionAtLeast(10, 0, 19041) {
+ fmt.Fprint(os.Stderr, wslOldVersion)
+ return errors.Errorf("WSL can not be automatically installed")
+ }
+
+ message := "WSL is not installed on this system, installing it.\n\n"
+
+ if !admin {
+ message += "Since you are not running as admin, a new window will open and " +
+ "require you to approve administrator privileges.\n\n"
+ }
+
+ message += "NOTE: A system reboot will be required as part of this process. " +
+ "If you prefer, you may abort now, and perform a manual installation using the \"wsl --install\" command."
+
+ if !opts.ReExec && MessageBox(message, "Podman Machine", false) != 1 {
+ return errors.Errorf("WSL installation aborted")
+ }
+
+ if !opts.ReExec && !admin {
+ return launchElevate("install the Windows WSL Features")
+ }
+
+ return installWsl()
+}
+
+func launchElevate(operation string) error {
+ truncateElevatedOutputFile()
+ err := relaunchElevatedWait()
+ if err != nil {
+ if eerr, ok := err.(*ExitCodeError); ok {
+ if eerr.code == ErrorSuccessRebootRequired {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ return nil
+ }
+ }
+
+ fmt.Fprintf(os.Stderr, "Elevated process failed with error: %v\n\n", err)
+ dumpOutputFile()
+ fmt.Fprintf(os.Stderr, wslInstallError, operation)
+ }
+ return err
+}
+
+func installWsl() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+ if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable WSL Feature")
+ }
+
+ if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
+ "/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
+ return errors.Wrap(err, "could not enable Virtual Machine Feature")
+ }
+ log.Close()
+
+ return reboot()
+}
+
+func installWslKernel() error {
+ log, err := getElevatedOutputFileWrite()
+ if err != nil {
+ return err
+ }
+ defer log.Close()
+
+ message := "Installing WSL Kernel Update"
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+
+ backoff := 500 * time.Millisecond
+ for i := 0; i < 5; i++ {
+ err = runCmdPassThroughTee(log, "wsl", "--update")
+ if err == nil {
+ break
+ }
+ // In case of unusual circumstances (e.g. race with installer actions)
+ // retry a few times
+ message = "An error occured attempting the WSL Kernel update, retrying..."
+ fmt.Println(message)
+ fmt.Fprintln(log, message)
+ time.Sleep(backoff)
+ backoff *= 2
+ }
+
+ if err != nil {
+ return errors.Wrap(err, "could not install WSL Kernel")
+ }
+
+ return nil
+}
+
+func getElevatedOutputFileName() (string, error) {
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(dir, "podman-elevated-output.log"), nil
+}
+
+func dumpOutputFile() {
+ file, err := getElevatedOutputFileRead()
+ if err != nil {
+ logrus.Debug("could not find elevated child output file")
+ return
+ }
+ defer file.Close()
+ _, _ = io.Copy(os.Stdout, file)
+}
+
+func getElevatedOutputFileRead() (*os.File, error) {
+ return getElevatedOutputFile(os.O_RDONLY)
+}
+
+func getElevatedOutputFileWrite() (*os.File, error) {
+ return getElevatedOutputFile(os.O_WRONLY | os.O_CREATE | os.O_APPEND)
+}
+
+func appendOutputIfError(write bool, err error) {
+ if write && err == nil {
+ return
+ }
+
+ if file, check := getElevatedOutputFileWrite(); check == nil {
+ defer file.Close()
+ fmt.Fprintf(file, "Error: %v\n", err)
+ }
+}
+
+func truncateElevatedOutputFile() error {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return err
+ }
+
+ return os.Truncate(name, 0)
+}
+
+func getElevatedOutputFile(mode int) (*os.File, error) {
+ name, err := getElevatedOutputFileName()
+ if err != nil {
+ return nil, err
+ }
+
+ dir, err := homedir.GetDataHome()
+ if err != nil {
+ return nil, err
+ }
+
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return nil, err
+ }
+
+ return os.OpenFile(name, mode, 0644)
+}
+
+func isMsiError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ if eerr, ok := err.(*exec.ExitError); ok {
+ switch eerr.ExitCode() {
+ case 0:
+ fallthrough
+ case ErrorSuccessRebootInitiated:
+ fallthrough
+ case ErrorSuccessRebootRequired:
+ return false
+ }
+ }
+
+ return true
+}
+func toDist(name string) string {
+ if !strings.HasPrefix(name, "podman") {
+ name = "podman-" + name
+ }
+ return name
+}
+
+func withUser(s string, user string) string {
+ return strings.ReplaceAll(s, "[USER]", user)
+}
+
+func runCmdPassThrough(name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+
+ // TODO - Perhaps improve this with a conpty pseudo console so that
+ // dism installer text bars mirror console behavior (redraw)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = io.MultiWriter(os.Stdout, out)
+ cmd.Stderr = io.MultiWriter(os.Stderr, out)
+ return cmd.Run()
+}
+
+func pipeCmdPassThrough(name string, input string, arg ...string) error {
+ logrus.Debugf("Running command: %s %v", name, arg)
+ cmd := exec.Command(name, arg...)
+ cmd.Stdin = strings.NewReader(input)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
+ if v.isRunning() {
+ return errors.Errorf("%q is already running", name)
+ }
+
+ fmt.Println("Starting machine...")
+
+ dist := toDist(name)
+
+ err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap")
+ if err != nil {
+ return errors.Wrap(err, "WSL bootstrap script failed")
+ }
+
+ return markStart(name)
+}
+
+func isWSLInstalled() bool {
+ cmd := exec.Command("wsl", "--status")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false
+ }
+ if err = cmd.Start(); err != nil {
+ return false
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := true
+ for scanner.Scan() {
+ line := scanner.Text()
+ // Windows 11 does not set an error exit code when a kernel is not avail
+ if strings.Contains(line, "kernel file is not found") {
+ result = false
+ break
+ }
+ }
+ if err := cmd.Wait(); !result || err != nil {
+ return false
+ }
+
+ return true
+}
+
+func isWSLFeatureEnabled() bool {
+ cmd := exec.Command("wsl", "--set-default-version", "2")
+ return cmd.Run() == nil
+}
+
+func isWSLRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-l", "--running")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
+ result := false
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) > 0 && dist == fields[0] {
+ result = true
+ break
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func isSystemdRunning(dist string) (bool, error) {
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return false, err
+ }
+ if err = cmd.Start(); err != nil {
+ return false, err
+ }
+ scanner := bufio.NewScanner(out)
+ result := false
+ if scanner.Scan() {
+ text := scanner.Text()
+ i, err := strconv.Atoi(text)
+ if err == nil && i > 0 {
+ result = true
+ }
+ }
+
+ _ = cmd.Wait()
+
+ return result, nil
+}
+
+func (v *MachineVM) Stop(name string, _ machine.StopOptions) error {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return err
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+ if err != nil {
+ return err
+ }
+ }
+
+ if !wsl || !sysd {
+ return errors.Errorf("%q is not running", v.Name)
+ }
+
+ cmd := exec.Command("wsl", "-d", dist, "sh")
+ cmd.Stdin = strings.NewReader(waitTerm)
+ if err = cmd.Start(); err != nil {
+ return errors.Wrap(err, "Error executing wait command")
+ }
+
+ exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
+ if err = exitCmd.Run(); err != nil {
+ return errors.Wrap(err, "Error stopping sysd")
+ }
+
+ if err = cmd.Wait(); err != nil {
+ return err
+ }
+
+ cmd = exec.Command("wsl", "--terminate", dist)
+ if err = cmd.Run(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+//nolint:cyclop
+func (v *MachineVM) Remove(name string, opts machine.RemoveOptions) (string, func() error, error) {
+ var files []string
+
+ if v.isRunning() {
+ return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name)
+ }
+
+ // Collect all the files that need to be destroyed
+ if !opts.SaveKeys {
+ files = append(files, v.IdentityPath, v.IdentityPath+".pub")
+ }
+ if !opts.SaveImage {
+ files = append(files, v.ImagePath)
+ }
+
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmConfigDir, v.Name+".json"))
+
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return "", nil, err
+ }
+ files = append(files, filepath.Join(vmDataDir, "wsldist", v.Name))
+
+ confirmationMessage := "\nThe following files will be deleted:\n\n"
+ for _, msg := range files {
+ confirmationMessage += msg + "\n"
+ }
+
+ confirmationMessage += "\n"
+ return confirmationMessage, func() error {
+ if err := machine.RemoveConnection(v.Name); err != nil {
+ logrus.Error(err)
+ }
+ if err := machine.RemoveConnection(v.Name + "-root"); err != nil {
+ logrus.Error(err)
+ }
+ if err := runCmdPassThrough("wsl", "--unregister", toDist(v.Name)); err != nil {
+ logrus.Error(err)
+ }
+ for _, f := range files {
+ if err := os.RemoveAll(f); err != nil {
+ logrus.Error(err)
+ }
+ }
+ return nil
+ }, nil
+}
+
+func (v *MachineVM) isRunning() bool {
+ dist := toDist(v.Name)
+
+ wsl, err := isWSLRunning(dist)
+ if err != nil {
+ return false
+ }
+
+ sysd := false
+ if wsl {
+ sysd, err = isSystemdRunning(dist)
+
+ if err != nil {
+ return false
+ }
+ }
+
+ return sysd
+}
+
+// SSH opens an interactive SSH session to the vm specified.
+// Added ssh function to VM interface: pkg/machine/config/go : line 58
+func (v *MachineVM) SSH(name string, opts machine.SSHOptions) error {
+ if !v.isRunning() {
+ return errors.Errorf("vm %q is not running.", v.Name)
+ }
+
+ username := opts.Username
+ if username == "" {
+ username = v.RemoteUsername
+ }
+
+ sshDestination := username + "@localhost"
+ port := strconv.Itoa(v.Port)
+
+ args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile /dev/null", "-o", "StrictHostKeyChecking no"}
+ if len(opts.Args) > 0 {
+ args = append(args, opts.Args...)
+ } else {
+ fmt.Printf("Connecting to vm %s. To close connection, use `~.` or `exit`\n", v.Name)
+ }
+
+ cmd := exec.Command("ssh", args...)
+ logrus.Debugf("Executing: ssh %v\n", args)
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Stdin = os.Stdin
+
+ return cmd.Run()
+}
+
+// List lists all vm's that use qemu virtualization
+func (p *Provider) List(_ machine.ListOptions) ([]*machine.ListResponse, error) {
+ return GetVMInfos()
+}
+
+func GetVMInfos() ([]*machine.ListResponse, error) {
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return nil, err
+ }
+
+ var listed []*machine.ListResponse
+
+ if err = filepath.Walk(vmConfigDir, func(path string, info os.FileInfo, err error) error {
+ vm := new(MachineVM)
+ if strings.HasSuffix(info.Name(), ".json") {
+ fullPath := filepath.Join(vmConfigDir, info.Name())
+ b, err := ioutil.ReadFile(fullPath)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(b, vm)
+ if err != nil {
+ return err
+ }
+ listEntry := new(machine.ListResponse)
+
+ listEntry.Name = vm.Name
+ listEntry.Stream = vm.ImageStream
+ listEntry.VMType = "wsl"
+ listEntry.CPUs, _ = getCPUs(vm)
+ listEntry.Memory, _ = getMem(vm)
+ listEntry.DiskSize = getDiskSize(vm)
+ fi, err := os.Stat(fullPath)
+ if err != nil {
+ return err
+ }
+ listEntry.CreatedAt = fi.ModTime()
+ listEntry.LastUp = getLastStart(vm, fi.ModTime())
+ if vm.isRunning() {
+ listEntry.Running = true
+ }
+
+ listed = append(listed, listEntry)
+ }
+ return nil
+ }); err != nil {
+ return nil, err
+ }
+ return listed, err
+}
+
+func getDiskSize(vm *MachineVM) uint64 {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return 0
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ disk := filepath.Join(distDir, vm.Name, "ext4.vhdx")
+ info, err := os.Stat(disk)
+ if err != nil {
+ return 0
+ }
+ return uint64(info.Size())
+}
+
+func markStart(name string) error {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return err
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, name, "laststart")
+ file, err := os.Create(start)
+ if err != nil {
+ return err
+ }
+ file.Close()
+
+ return nil
+}
+
+func getLastStart(vm *MachineVM, created time.Time) time.Time {
+ vmDataDir, err := machine.GetDataDir(vmtype)
+ if err != nil {
+ return created
+ }
+ distDir := filepath.Join(vmDataDir, "wsldist")
+ start := filepath.Join(distDir, vm.Name, "laststart")
+ info, err := os.Stat(start)
+ if err != nil {
+ return created
+ }
+ return info.ModTime()
+}
+
+func getCPUs(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "nproc")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var result string
+ for scanner.Scan() {
+ result = scanner.Text()
+ }
+ _ = cmd.Wait()
+
+ ret, err := strconv.Atoi(result)
+ return uint64(ret), err
+}
+
+func getMem(vm *MachineVM) (uint64, error) {
+ dist := toDist(vm.Name)
+ if run, _ := isWSLRunning(dist); !run {
+ return 0, nil
+ }
+ cmd := exec.Command("wsl", "-d", dist, "cat", "/proc/meminfo")
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ return 0, err
+ }
+ if err = cmd.Start(); err != nil {
+ return 0, err
+ }
+ scanner := bufio.NewScanner(out)
+ var (
+ total, available uint64
+ t, a int
+ )
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if strings.HasPrefix(fields[0], "MemTotal") && len(fields) >= 2 {
+ t, err = strconv.Atoi(fields[1])
+ total = uint64(t) * 1024
+ } else if strings.HasPrefix(fields[0], "MemAvailable") && len(fields) >= 2 {
+ a, err = strconv.Atoi(fields[1])
+ available = uint64(a) * 1024
+ }
+ if err != nil {
+ break
+ }
+ }
+ _ = cmd.Wait()
+
+ return total - available, err
+}
+
+func (p *Provider) IsValidVMName(name string) (bool, error) {
+ infos, err := GetVMInfos()
+ if err != nil {
+ return false, err
+ }
+ for _, vm := range infos {
+ if vm.Name == name {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
+ return false, "", nil
+}
diff --git a/pkg/machine/wsl/machine_unsupported.go b/pkg/machine/wsl/machine_unsupported.go
new file mode 100644
index 000000000..043c5d729
--- /dev/null
+++ b/pkg/machine/wsl/machine_unsupported.go
@@ -0,0 +1,3 @@
+// +build !windows
+
+package wsl
diff --git a/pkg/machine/wsl/util_windows.go b/pkg/machine/wsl/util_windows.go
new file mode 100644
index 000000000..95e4c9894
--- /dev/null
+++ b/pkg/machine/wsl/util_windows.go
@@ -0,0 +1,338 @@
+package wsl
+
+import (
+ "encoding/base64"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/registry"
+
+ "github.com/containers/storage/pkg/homedir"
+)
+
+//nolint
+type SHELLEXECUTEINFO struct {
+ cbSize uint32
+ fMask uint32
+ hwnd syscall.Handle
+ lpVerb uintptr
+ lpFile uintptr
+ lpParameters uintptr
+ lpDirectory uintptr
+ nShow int
+ hInstApp syscall.Handle
+ lpIDList uintptr
+ lpClass uintptr
+ hkeyClass syscall.Handle
+ dwHotKey uint32
+ hIconOrMonitor syscall.Handle
+ hProcess syscall.Handle
+}
+
+//nolint
+type Luid struct {
+ lowPart uint32
+ highPart int32
+}
+
+type LuidAndAttributes struct {
+ luid Luid
+ attributes uint32
+}
+
+type TokenPrivileges struct {
+ privilegeCount uint32
+ privileges [1]LuidAndAttributes
+}
+
+//nolint // Cleaner to refer to the official OS constant names, and consistent with syscall
+const (
+ SEE_MASK_NOCLOSEPROCESS = 0x40
+ EWX_FORCEIFHUNG = 0x10
+ EWX_REBOOT = 0x02
+ EWX_RESTARTAPPS = 0x40
+ SHTDN_REASON_MAJOR_APPLICATION = 0x00040000
+ SHTDN_REASON_MINOR_INSTALLATION = 0x00000002
+ SHTDN_REASON_FLAG_PLANNED = 0x80000000
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+ TOKEN_QUERY = 0x0008
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ SE_ERR_ACCESSDENIED = 0x05
+)
+
+func winVersionAtLeast(major uint, minor uint, build uint) bool {
+ var out [3]uint32
+
+ in := []uint32{uint32(major), uint32(minor), uint32(build)}
+ out[0], out[1], out[2] = windows.RtlGetNtVersionNumbers()
+
+ for i, o := range out {
+ if in[i] > o {
+ return false
+ }
+ if in[i] < o {
+ return true
+ }
+ }
+
+ return true
+}
+
+func hasAdminRights() bool {
+ var sid *windows.SID
+
+ // See: https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
+ if err := windows.AllocateAndInitializeSid(
+ &windows.SECURITY_NT_AUTHORITY,
+ 2,
+ windows.SECURITY_BUILTIN_DOMAIN_RID,
+ windows.DOMAIN_ALIAS_RID_ADMINS,
+ 0, 0, 0, 0, 0, 0,
+ &sid); err != nil {
+ logrus.Warnf("SID allocation error: %s", err)
+ return false
+ }
+ defer windows.FreeSid(sid)
+
+ // From MS docs:
+ // "If TokenHandle is NULL, CheckTokenMembership uses the impersonation
+ // token of the calling thread. If the thread is not impersonating,
+ // the function duplicates the thread's primary token to create an
+ // impersonation token."
+ token := windows.Token(0)
+
+ member, err := token.IsMember(sid)
+ if err != nil {
+ logrus.Warnf("Token Membership Error: %s", err)
+ return false
+ }
+
+ return member || token.IsElevated()
+}
+
+func relaunchElevatedWait() error {
+ e, _ := os.Executable()
+ d, _ := os.Getwd()
+ exe, _ := syscall.UTF16PtrFromString(e)
+ cwd, _ := syscall.UTF16PtrFromString(d)
+ arg, _ := syscall.UTF16PtrFromString(buildCommandArgs(true))
+ verb, _ := syscall.UTF16PtrFromString("runas")
+
+ shell32 := syscall.NewLazyDLL("shell32.dll")
+
+ info := &SHELLEXECUTEINFO{
+ fMask: SEE_MASK_NOCLOSEPROCESS,
+ hwnd: 0,
+ lpVerb: uintptr(unsafe.Pointer(verb)),
+ lpFile: uintptr(unsafe.Pointer(exe)),
+ lpParameters: uintptr(unsafe.Pointer(arg)),
+ lpDirectory: uintptr(unsafe.Pointer(cwd)),
+ nShow: 1,
+ }
+ info.cbSize = uint32(unsafe.Sizeof(*info))
+ procShellExecuteEx := shell32.NewProc("ShellExecuteExW")
+ if ret, _, _ := procShellExecuteEx.Call(uintptr(unsafe.Pointer(info))); ret == 0 { // 0 = False
+ err := syscall.GetLastError()
+ if info.hInstApp == SE_ERR_ACCESSDENIED {
+ return wrapMaybe(err, "request to elevate privileges was denied")
+ }
+ return wrapMaybef(err, "could not launch process, ShellEX Error = %d", info.hInstApp)
+ }
+
+ handle := syscall.Handle(info.hProcess)
+ defer syscall.CloseHandle(handle)
+
+ w, err := syscall.WaitForSingleObject(handle, syscall.INFINITE)
+ switch w {
+ case syscall.WAIT_OBJECT_0:
+ break
+ case syscall.WAIT_FAILED:
+ return errors.Wrap(err, "could not wait for process, failed")
+ default:
+ return errors.Errorf("could not wait for process, unknown error")
+ }
+ var code uint32
+ if err := syscall.GetExitCodeProcess(handle, &code); err != nil {
+ return err
+ }
+ if code != 0 {
+ return &ExitCodeError{uint(code)}
+ }
+
+ return nil
+}
+
+func wrapMaybe(err error, message string) error {
+ if err != nil {
+ return errors.Wrap(err, message)
+ }
+
+ return errors.New(message)
+}
+
+func wrapMaybef(err error, format string, args ...interface{}) error {
+ if err != nil {
+ return errors.Wrapf(err, format, args...)
+ }
+
+ return errors.Errorf(format, args...)
+}
+
+func reboot() error {
+ const (
+ wtLocation = `Microsoft\WindowsApps\wt.exe`
+ wtPrefix = `%LocalAppData%\Microsoft\WindowsApps\wt -p "Windows PowerShell" `
+ localAppData = "LocalAppData"
+ pShellLaunch = `powershell -noexit "powershell -EncodedCommand (Get-Content '%s')"`
+ )
+
+ exe, _ := os.Executable()
+ relaunch := fmt.Sprintf("& %s %s", syscall.EscapeArg(exe), buildCommandArgs(false))
+ encoded := base64.StdEncoding.EncodeToString(encodeUTF16Bytes(relaunch))
+
+ dataDir, err := homedir.GetDataHome()
+ if err != nil {
+ return errors.Wrap(err, "could not determine data directory")
+ }
+ if err := os.MkdirAll(dataDir, 0755); err != nil {
+ return errors.Wrap(err, "could not create data directory")
+ }
+ commFile := filepath.Join(dataDir, "podman-relaunch.dat")
+ if err := ioutil.WriteFile(commFile, []byte(encoded), 0600); err != nil {
+ return errors.Wrap(err, "could not serialize command state")
+ }
+
+ command := fmt.Sprintf(pShellLaunch, commFile)
+ if _, err := os.Lstat(filepath.Join(os.Getenv(localAppData), wtLocation)); err == nil {
+ wtCommand := wtPrefix + command
+ // RunOnce is limited to 260 chars (supposedly no longer in Builds >= 19489)
+ // For now fallbacak in cases of long usernames (>89 chars)
+ if len(wtCommand) < 260 {
+ command = wtCommand
+ }
+ }
+
+ if err := addRunOnceRegistryEntry(command); err != nil {
+ return err
+ }
+
+ if err := obtainShutdownPrivilege(); err != nil {
+ return err
+ }
+
+ message := "To continue the process of enabling WSL, the system needs to reboot. " +
+ "Alternatively, you can cancel and reboot manually\n\n" +
+ "After rebooting, please wait a minute or two for podman machine to relaunch and continue installing."
+
+ if MessageBox(message, "Podman Machine", false) != 1 {
+ fmt.Println("Reboot is required to continue installation, please reboot at your convenience")
+ os.Exit(ErrorSuccessRebootRequired)
+ return nil
+ }
+
+ user32 := syscall.NewLazyDLL("user32")
+ procExit := user32.NewProc("ExitWindowsEx")
+ if ret, _, err := procExit.Call(EWX_REBOOT|EWX_RESTARTAPPS|EWX_FORCEIFHUNG,
+ SHTDN_REASON_MAJOR_APPLICATION|SHTDN_REASON_MINOR_INSTALLATION|SHTDN_REASON_FLAG_PLANNED); ret != 1 {
+ return errors.Wrap(err, "reboot failed")
+ }
+
+ return nil
+}
+
+func obtainShutdownPrivilege() error {
+ const SeShutdownName = "SeShutdownPrivilege"
+
+ advapi32 := syscall.NewLazyDLL("advapi32")
+ OpenProcessToken := advapi32.NewProc("OpenProcessToken")
+ LookupPrivilegeValue := advapi32.NewProc("LookupPrivilegeValueW")
+ AdjustTokenPrivileges := advapi32.NewProc("AdjustTokenPrivileges")
+
+ proc, _ := syscall.GetCurrentProcess()
+
+ var hToken uintptr
+ if ret, _, err := OpenProcessToken.Call(uintptr(proc), TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken))); ret != 1 {
+ return errors.Wrap(err, "error opening process token")
+ }
+
+ var privs TokenPrivileges
+ if ret, _, err := LookupPrivilegeValue.Call(uintptr(0), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(SeShutdownName))), uintptr(unsafe.Pointer(&(privs.privileges[0].luid)))); ret != 1 {
+ return errors.Wrap(err, "error looking up shutdown privilege")
+ }
+
+ privs.privilegeCount = 1
+ privs.privileges[0].attributes = SE_PRIVILEGE_ENABLED
+
+ if ret, _, err := AdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&privs)), 0, uintptr(0), 0); ret != 1 {
+ return errors.Wrap(err, "error enabling shutdown privilege on token")
+ }
+
+ return nil
+}
+
+func addRunOnceRegistryEntry(command string) error {
+ k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\RunOnce`, registry.WRITE)
+ if err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ defer k.Close()
+
+ if err := k.SetExpandStringValue("podman-machine", command); err != nil {
+ return errors.Wrap(err, "could not open RunOnce registry entry")
+ }
+
+ return nil
+}
+
+func encodeUTF16Bytes(s string) []byte {
+ u16 := utf16.Encode([]rune(s))
+ u16le := make([]byte, len(u16)*2)
+ for i := 0; i < len(u16); i++ {
+ u16le[i<<1] = byte(u16[i])
+ u16le[(i<<1)+1] = byte(u16[i] >> 8)
+ }
+ return u16le
+}
+
+func MessageBox(caption, title string, fail bool) int {
+ var format int
+ if fail {
+ format = 0x10
+ } else {
+ format = 0x41
+ }
+
+ user32 := syscall.NewLazyDLL("user32.dll")
+ captionPtr, _ := syscall.UTF16PtrFromString(caption)
+ titlePtr, _ := syscall.UTF16PtrFromString(title)
+ ret, _, _ := user32.NewProc("MessageBoxW").Call(
+ uintptr(0),
+ uintptr(unsafe.Pointer(captionPtr)),
+ uintptr(unsafe.Pointer(titlePtr)),
+ uintptr(format))
+
+ return int(ret)
+}
+
+func buildCommandArgs(elevate bool) string {
+ var args []string
+ for _, arg := range os.Args[1:] {
+ if arg != "--reexec" {
+ args = append(args, syscall.EscapeArg(arg))
+ if elevate && arg == "init" {
+ args = append(args, "--reexec")
+ }
+ }
+ }
+ return strings.Join(args, " ")
+}
diff --git a/pkg/network/network.go b/pkg/network/network.go
deleted file mode 100644
index 44132ca28..000000000
--- a/pkg/network/network.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package network
-
-import (
- "crypto/sha256"
- "encoding/hex"
- "strings"
-
- "github.com/containernetworking/cni/libcni"
-)
-
-// GetCNIPlugins returns a list of plugins that a given network
-// has in the form of a string
-func GetCNIPlugins(list *libcni.NetworkConfigList) string {
- plugins := make([]string, 0, len(list.Plugins))
- for _, plug := range list.Plugins {
- plugins = append(plugins, plug.Network.Type)
- }
- return strings.Join(plugins, ",")
-}
-
-// GetNetworkID return the network ID for a given name.
-// It is just the sha256 hash but this should be good enough.
-// The caller has to make sure it is only called with the network name.
-func GetNetworkID(name string) string {
- hash := sha256.Sum256([]byte(name))
- return hex.EncodeToString(hash[:])
-}
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index 90ad23f49..a1d77e785 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -74,7 +74,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp
}
}
- if options.All && options.External {
+ if options.External {
listCon, err := GetExternalContainerLists(runtime)
if err != nil {
return nil, err
@@ -207,7 +207,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
return entities.ListContainer{}, err
}
- networks, _, err := ctr.Networks()
+ networks, err := ctr.Networks()
if err != nil {
return entities.ListContainer{}, err
}
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/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 3e81d5c14..92725adc7 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -145,8 +145,8 @@ func tryMappingTool(uid bool, pid int, hostID int, mappings []idtools.IDMap) err
}
if output, err := cmd.CombinedOutput(); err != nil {
- logrus.Debugf("error from %s: %s", tool, output)
- return errors.Wrapf(err, "cannot setup namespace using %s", tool)
+ logrus.Errorf("error running `%s`: %s", strings.Join(args, " "), output)
+ return errors.Wrapf(err, "cannot setup namespace using %q", path)
}
return nil
}
diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go
index caea51ea8..d06a047c1 100644
--- a/pkg/specgen/container_validate.go
+++ b/pkg/specgen/container_validate.go
@@ -29,25 +29,10 @@ func exclusiveOptions(opt1, opt2 string) error {
// Validate verifies that the given SpecGenerator is valid and satisfies required
// input for creating a container.
func (s *SpecGenerator) Validate() error {
- if rootless.IsRootless() && len(s.CNINetworks) == 0 {
- if s.StaticIP != nil || s.StaticIPv6 != nil {
- return ErrNoStaticIPRootless
- }
- if s.StaticMAC != nil {
- return ErrNoStaticMACRootless
- }
- }
-
// Containers being added to a pod cannot have certain network attributes
// associated with them because those should be on the infra container.
if len(s.Pod) > 0 && s.NetNS.NSMode == FromPod {
- if s.StaticIP != nil || s.StaticIPv6 != nil {
- return errors.Wrap(define.ErrNetworkOnPodContainer, "static ip addresses must be defined when the pod is created")
- }
- if s.StaticMAC != nil {
- return errors.Wrap(define.ErrNetworkOnPodContainer, "MAC addresses must be defined when the pod is created")
- }
- if len(s.CNINetworks) > 0 {
+ if len(s.Networks) > 0 {
return errors.Wrap(define.ErrNetworkOnPodContainer, "networks must be defined when the pod is created")
}
if len(s.PortMappings) > 0 || s.PublishExposedPorts {
@@ -204,5 +189,10 @@ func (s *SpecGenerator) Validate() error {
if err := validateNetNS(&s.NetNS); err != nil {
return err
}
+ if s.NetNS.NSMode != Bridge && len(s.Networks) > 0 {
+ // Note that we also get the ip and mac in the networks map
+ return errors.New("Networks and static ip/mac address can only be used with Bridge mode networking")
+ }
+
return nil
}
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
index 40a18a6ac..57676db10 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -156,7 +156,9 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
// Add annotations from the image
for k, v := range inspectData.Annotations {
- annotations[k] = v
+ if !define.IsReservedAnnotation(k) {
+ annotations[k] = v
+ }
}
}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index df5d2e8ff..7d792b3b1 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -2,13 +2,15 @@ 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"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -28,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
}
@@ -96,6 +85,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err
}
s.UserNS = defaultNS
+
+ mappings, err := util.ParseIDMapping(namespaces.UsernsMode(s.UserNS.NSMode), nil, nil, "", "")
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ s.IDMappings = mappings
}
if s.NetNS.IsDefault() {
defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod)
@@ -112,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))
}
@@ -149,21 +142,22 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, nil, nil, err
}
+ if len(s.HostUsers) > 0 {
+ options = append(options, libpod.WithHostUsers(s.HostUsers))
+ }
+
command, err := makeCommand(ctx, s, imageData, rtc)
if err != nil {
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
}
options = append(options, opts...)
- if len(s.Aliases) > 0 {
- options = append(options, libpod.WithNetworkAliases(s.Aliases))
- }
-
if containerType := s.InitContainerType; len(containerType) > 0 {
options = append(options, libpod.WithInitCtrType(containerType))
}
@@ -171,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) {
@@ -203,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
@@ -217,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
@@ -310,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
@@ -398,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 {
@@ -486,5 +482,38 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if s.PidFile != "" {
options = append(options, libpod.WithPidFile(s.PidFile))
}
+
+ options = append(options, libpod.WithSelectedPasswordManagement(s.Passwd))
+
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/namespaces.go b/pkg/specgen/generate/namespaces.go
index 7d63fc10f..a2bc37e34 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
@@ -66,7 +67,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
case "cgroup":
return specgen.ParseCgroupNamespace(cfg.Containers.CgroupNS)
case "net":
- ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS, cfg.Containers.RootlessNetworking == "cni")
+ ns, _, _, err := specgen.ParseNetworkFlag(nil)
return ns, err
}
@@ -250,7 +251,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if s.NetNS.Value != "" {
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
}
- toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, s.CNINetworks))
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil))
case specgen.Private:
fallthrough
case specgen.Bridge:
@@ -258,7 +259,34 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if err != nil {
return nil, err
}
- toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.CNINetworks))
+
+ rtConfig, err := rt.GetConfigNoCopy()
+ if err != nil {
+ return nil, err
+ }
+ // if no network was specified use add the default
+ if len(s.Networks) == 0 {
+ // backwards config still allow the old cni networks list and convert to new format
+ if len(s.CNINetworks) > 0 {
+ logrus.Warn(`specgen "cni_networks" option is deprecated use the "networks" map instead`)
+ networks := make(map[string]types.PerNetworkOptions, len(s.CNINetworks))
+ for _, net := range s.CNINetworks {
+ networks[net] = types.PerNetworkOptions{}
+ }
+ s.Networks = networks
+ } else {
+ // no networks given but bridge is set so use default network
+ s.Networks = map[string]types.PerNetworkOptions{
+ rtConfig.Network.DefaultNetwork: {},
+ }
+ }
+ }
+ // rename the "default" network to the correct default name
+ if opts, ok := s.Networks["default"]; ok {
+ s.Networks[rtConfig.Network.DefaultNetwork] = opts
+ delete(s.Networks, "default")
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.Networks))
}
if s.UseImageHosts {
@@ -281,12 +309,6 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if len(s.DNSOptions) > 0 {
toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions))
}
- if s.StaticIP != nil {
- toReturn = append(toReturn, libpod.WithStaticIP(*s.StaticIP))
- }
- if s.StaticMAC != nil {
- toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC))
- }
if s.NetworkOptions != nil {
toReturn = append(toReturn, libpod.WithNetworkOptions(s.NetworkOptions))
}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 9f8807915..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,17 +340,26 @@ 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
- for _, dev := range s.DeviceCGroupRule {
- g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access)
+ // set the devices cgroup when not running in a user namespace
+ if !inUserNS && !s.Privileged {
+ g.AddLinuxResourcesDevice(false, "", nil, nil, "rwm")
+ for _, dev := range s.DeviceCGroupRule {
+ g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access)
+ }
}
for k, v := range s.WeightDevice {
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index 72dd249e7..0a797c571 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -218,9 +218,7 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
case specgen.Host:
logrus.Debugf("Pod will use host networking")
if len(p.InfraContainerSpec.PortMappings) > 0 ||
- p.InfraContainerSpec.StaticIP != nil ||
- p.InfraContainerSpec.StaticMAC != nil ||
- len(p.InfraContainerSpec.CNINetworks) > 0 ||
+ len(p.InfraContainerSpec.Networks) > 0 ||
p.InfraContainerSpec.NetNS.NSMode == specgen.NoNetwork {
return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set host network if network-related configuration is specified")
}
@@ -234,9 +232,7 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
case specgen.NoNetwork:
logrus.Debugf("Pod will not use networking")
if len(p.InfraContainerSpec.PortMappings) > 0 ||
- p.InfraContainerSpec.StaticIP != nil ||
- p.InfraContainerSpec.StaticMAC != nil ||
- len(p.InfraContainerSpec.CNINetworks) > 0 ||
+ len(p.InfraContainerSpec.Networks) > 0 ||
p.InfraContainerSpec.NetNS.NSMode == "host" {
return nil, errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified")
}
@@ -264,15 +260,13 @@ func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
if len(p.DNSSearch) > 0 {
p.InfraContainerSpec.DNSSearch = p.DNSSearch
}
- if p.StaticIP != nil {
- p.InfraContainerSpec.StaticIP = p.StaticIP
- }
- if p.StaticMAC != nil {
- p.InfraContainerSpec.StaticMAC = p.StaticMAC
- }
if p.NoManageResolvConf {
p.InfraContainerSpec.UseImageResolvConf = true
}
+ if len(p.Networks) > 0 {
+ p.InfraContainerSpec.Networks = p.Networks
+ }
+ // deprecated cni networks for api users
if len(p.CNINetworks) > 0 {
p.InfraContainerSpec.CNINetworks = p.CNINetworks
}
diff --git a/pkg/specgen/generate/validate.go b/pkg/specgen/generate/validate.go
index a44bf9979..c74db7325 100644
--- a/pkg/specgen/generate/validate.go
+++ b/pkg/specgen/generate/validate.go
@@ -60,10 +60,6 @@ func verifyContainerResourcesCgroupV1(s *specgen.SpecGenerator) ([]string, error
if memory.Limit != nil && memory.Reservation != nil && *memory.Limit < *memory.Reservation {
return warnings, errors.New("minimum memory limit cannot be less than memory reservation limit, see usage")
}
- if memory.Kernel != nil && !sysInfo.KernelMemory {
- warnings = append(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.")
- memory.Kernel = nil
- }
if memory.DisableOOMKiller != nil && *memory.DisableOOMKiller && !sysInfo.OomKillDisable {
warnings = append(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.")
memory.DisableOOMKiller = nil
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index bb5385ef1..15a8ece17 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -2,10 +2,13 @@ package specgen
import (
"fmt"
+ "net"
"os"
"strings"
"github.com/containers/common/pkg/cgroups"
+ "github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/libpod/network/types"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage"
@@ -271,9 +274,9 @@ func ParseUserNamespace(ns string) (Namespace, error) {
// ParseNetworkNamespace parses a network namespace specification in string
// form.
// Returns a namespace and (optionally) a list of CNI networks to join.
-func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, []string, error) {
+func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, map[string]types.PerNetworkOptions, error) {
toReturn := Namespace{}
- var cniNetworks []string
+ networks := make(map[string]types.PerNetworkOptions)
// Net defaults to Slirp on rootless
switch {
case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
@@ -313,28 +316,174 @@ func ParseNetworkNamespace(ns string, rootlessDefaultCNI bool) (Namespace, []str
default:
// Assume we have been given a list of CNI networks.
// Which only works in bridge mode, so set that.
- cniNetworks = strings.Split(ns, ",")
+ networkList := strings.Split(ns, ",")
+ for _, net := range networkList {
+ networks[net] = types.PerNetworkOptions{}
+ }
+
toReturn.NSMode = Bridge
}
- return toReturn, cniNetworks, nil
+ return toReturn, networks, nil
}
-func ParseNetworkString(network string) (Namespace, []string, map[string][]string, error) {
+// ParseNetworkFlag parses a network string slice into the network options
+// If the input is nil or empty it will use the default setting from containers.conf
+func ParseNetworkFlag(networks []string) (Namespace, map[string]types.PerNetworkOptions, map[string][]string, error) {
var networkOptions map[string][]string
- parts := strings.SplitN(network, ":", 2)
+ // by default we try to use the containers.conf setting
+ // if we get at least one value use this instead
+ ns := containerConfig.Containers.NetNS
+ if len(networks) > 0 {
+ ns = networks[0]
+ }
- ns, cniNets, err := ParseNetworkNamespace(network, containerConfig.Containers.RootlessNetworking == "cni")
- if err != nil {
- return Namespace{}, nil, nil, err
+ toReturn := Namespace{}
+ podmanNetworks := make(map[string]types.PerNetworkOptions)
+
+ switch {
+ case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
+ parts := strings.SplitN(ns, ":", 2)
+ if len(parts) > 1 {
+ networkOptions = make(map[string][]string)
+ networkOptions[parts[0]] = strings.Split(parts[1], ",")
+ }
+ toReturn.NSMode = Slirp
+ case ns == string(FromPod):
+ toReturn.NSMode = FromPod
+ case ns == "" || ns == string(Default) || ns == string(Private):
+ // Net defaults to Slirp on rootless
+ if rootless.IsRootless() && containerConfig.Containers.RootlessNetworking != "cni" {
+ toReturn.NSMode = Slirp
+ break
+ }
+ // if not slirp we use bridge
+ fallthrough
+ case ns == string(Bridge), strings.HasPrefix(ns, string(Bridge)+":"):
+ toReturn.NSMode = Bridge
+ parts := strings.SplitN(ns, ":", 2)
+ netOpts := types.PerNetworkOptions{}
+ if len(parts) > 1 {
+ var err error
+ netOpts, err = parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, err
+ }
+ }
+ // we have to set the special default network name here
+ podmanNetworks["default"] = netOpts
+
+ case ns == string(NoNetwork):
+ toReturn.NSMode = NoNetwork
+ case ns == string(Host):
+ toReturn.NSMode = Host
+ case strings.HasPrefix(ns, "ns:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, nil, errors.Errorf("must provide a path to a namespace when specifying ns:")
+ }
+ toReturn.NSMode = Path
+ toReturn.Value = split[1]
+ case strings.HasPrefix(ns, string(FromContainer)+":"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, nil, errors.Errorf("must provide name or ID or a container when specifying container:")
+ }
+ toReturn.NSMode = FromContainer
+ toReturn.Value = split[1]
+ default:
+ // we should have a normal network
+ parts := strings.SplitN(ns, ":", 2)
+ if len(parts) == 1 {
+ // Assume we have been given a comma separated list of networks for backwards compat.
+ networkList := strings.Split(ns, ",")
+ for _, net := range networkList {
+ podmanNetworks[net] = types.PerNetworkOptions{}
+ }
+ } else {
+ if parts[0] == "" {
+ return toReturn, nil, nil, errors.New("network name cannot be empty")
+ }
+ netOpts, err := parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
+ }
+ podmanNetworks[parts[0]] = netOpts
+ }
+
+ // networks need bridge mode
+ toReturn.NSMode = Bridge
+ }
+
+ if len(networks) > 1 {
+ if !toReturn.IsBridge() {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "cannot set multiple networks without bridge network mode, selected mode %s", toReturn.NSMode)
+ }
+
+ for _, network := range networks[1:] {
+ parts := strings.SplitN(network, ":", 2)
+ if parts[0] == "" {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "network name cannot be empty")
+ }
+ if util.StringInSlice(parts[0], []string{string(Bridge), string(Slirp), string(FromPod), string(NoNetwork),
+ string(Default), string(Private), string(Path), string(FromContainer), string(Host)}) {
+ return toReturn, nil, nil, errors.Wrapf(define.ErrInvalidArg, "can only set extra network names, selected mode %s conflicts with bridge", parts[0])
+ }
+ netOpts := types.PerNetworkOptions{}
+ if len(parts) > 1 {
+ var err error
+ netOpts, err = parseBridgeNetworkOptions(parts[1])
+ if err != nil {
+ return toReturn, nil, nil, errors.Wrapf(err, "invalid option for network %s", parts[0])
+ }
+ }
+ podmanNetworks[parts[0]] = netOpts
+ }
+ }
+
+ return toReturn, podmanNetworks, networkOptions, nil
+}
+
+func parseBridgeNetworkOptions(opts string) (types.PerNetworkOptions, error) {
+ netOpts := types.PerNetworkOptions{}
+ if len(opts) == 0 {
+ return netOpts, nil
}
+ allopts := strings.Split(opts, ",")
+ for _, opt := range allopts {
+ split := strings.SplitN(opt, "=", 2)
+ switch split[0] {
+ case "ip", "ip6":
+ ip := net.ParseIP(split[1])
+ if ip == nil {
+ return netOpts, errors.Errorf("invalid ip address %q", split[1])
+ }
+ netOpts.StaticIPs = append(netOpts.StaticIPs, ip)
- if len(parts) > 1 {
- networkOptions = make(map[string][]string)
- networkOptions[parts[0]] = strings.Split(parts[1], ",")
- cniNets = nil
+ case "mac":
+ mac, err := net.ParseMAC(split[1])
+ if err != nil {
+ return netOpts, err
+ }
+ netOpts.StaticMAC = types.HardwareAddr(mac)
+
+ case "alias":
+ if split[1] == "" {
+ return netOpts, errors.New("alias cannot be empty")
+ }
+ netOpts.Aliases = append(netOpts.Aliases, split[1])
+
+ case "interface_name":
+ if split[1] == "" {
+ return netOpts, errors.New("interface_name cannot be empty")
+ }
+ netOpts.InterfaceName = split[1]
+
+ default:
+ return netOpts, errors.Errorf("unknown bridge network option: %s", split[0])
+ }
}
- return ns, cniNets, networkOptions, nil
+ return netOpts, nil
}
func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) {
diff --git a/pkg/specgen/namespaces_test.go b/pkg/specgen/namespaces_test.go
new file mode 100644
index 000000000..4f69e6b98
--- /dev/null
+++ b/pkg/specgen/namespaces_test.go
@@ -0,0 +1,265 @@
+package specgen
+
+import (
+ "net"
+ "testing"
+
+ "github.com/containers/podman/v3/libpod/network/types"
+ "github.com/containers/podman/v3/pkg/rootless"
+ "github.com/stretchr/testify/assert"
+)
+
+func parsMacNoErr(mac string) types.HardwareAddr {
+ m, _ := net.ParseMAC(mac)
+ return types.HardwareAddr(m)
+}
+
+func TestParseNetworkFlag(t *testing.T) {
+ // root and rootless have different defaults
+ defaultNetName := "default"
+ defaultNetworks := map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ }
+ defaultNsMode := Namespace{NSMode: Bridge}
+ if rootless.IsRootless() {
+ defaultNsMode = Namespace{NSMode: Slirp}
+ defaultNetworks = map[string]types.PerNetworkOptions{}
+ }
+
+ tests := []struct {
+ name string
+ args []string
+ nsmode Namespace
+ networks map[string]types.PerNetworkOptions
+ options map[string][]string
+ err string
+ }{
+ {
+ name: "empty input",
+ args: nil,
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "empty string as input",
+ args: []string{},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "default mode",
+ args: []string{"default"},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "private mode",
+ args: []string{"private"},
+ nsmode: defaultNsMode,
+ networks: defaultNetworks,
+ },
+ {
+ name: "bridge mode",
+ args: []string{"bridge"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ },
+ },
+ {
+ name: "slirp4netns mode",
+ args: []string{"slirp4netns"},
+ nsmode: Namespace{NSMode: Slirp},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "from pod mode",
+ args: []string{"pod"},
+ nsmode: Namespace{NSMode: FromPod},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "no network mode",
+ args: []string{"none"},
+ nsmode: Namespace{NSMode: NoNetwork},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "container mode",
+ args: []string{"container:abc"},
+ nsmode: Namespace{NSMode: FromContainer, Value: "abc"},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "ns path mode",
+ args: []string{"ns:/path"},
+ nsmode: Namespace{NSMode: Path, Value: "/path"},
+ networks: map[string]types.PerNetworkOptions{},
+ },
+ {
+ name: "slirp4netns mode with options",
+ args: []string{"slirp4netns:cidr=10.0.0.0/24"},
+ nsmode: Namespace{NSMode: Slirp},
+ networks: map[string]types.PerNetworkOptions{},
+ options: map[string][]string{
+ "slirp4netns": {"cidr=10.0.0.0/24"},
+ },
+ },
+ {
+ name: "bridge mode with options 1",
+ args: []string{"bridge:ip=10.0.0.1,mac=11:22:33:44:55:66"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("10.0.0.1")},
+ StaticMAC: parsMacNoErr("11:22:33:44:55:66"),
+ },
+ },
+ },
+ {
+ name: "bridge mode with options 2",
+ args: []string{"bridge:ip=10.0.0.1,ip=10.0.0.5"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("10.0.0.1"), net.ParseIP("10.0.0.5")},
+ },
+ },
+ },
+ {
+ name: "bridge mode with ip6 option",
+ args: []string{"bridge:ip6=fd10::"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ StaticIPs: []net.IP{net.ParseIP("fd10::")},
+ },
+ },
+ },
+ {
+ name: "bridge mode with alias option",
+ args: []string{"bridge:alias=myname,alias=myname2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ Aliases: []string{"myname", "myname2"},
+ },
+ },
+ },
+ {
+ name: "bridge mode with alias option",
+ args: []string{"bridge:alias=myname,alias=myname2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ Aliases: []string{"myname", "myname2"},
+ },
+ },
+ },
+ {
+ name: "bridge mode with interface option",
+ args: []string{"bridge:interface_name=eth123"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {
+ InterfaceName: "eth123",
+ },
+ },
+ },
+ {
+ name: "bridge mode with invalid option",
+ args: []string{"bridge:abc=123"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "unknown bridge network option: abc",
+ },
+ {
+ name: "bridge mode with invalid ip",
+ args: []string{"bridge:ip=10..1"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "invalid ip address \"10..1\"",
+ },
+ {
+ name: "bridge mode with invalid mac",
+ args: []string{"bridge:mac=123"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "address 123: invalid MAC address",
+ },
+ {
+ name: "network name",
+ args: []string{"someName"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {},
+ },
+ },
+ {
+ name: "network name with options",
+ args: []string{"someName:ip=10.0.0.1"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {StaticIPs: []net.IP{net.ParseIP("10.0.0.1")}},
+ },
+ },
+ {
+ name: "multiple networks",
+ args: []string{"someName", "net2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {},
+ "net2": {},
+ },
+ },
+ {
+ name: "multiple networks with options",
+ args: []string{"someName:ip=10.0.0.1", "net2:ip=10.10.0.1"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ "someName": {StaticIPs: []net.IP{net.ParseIP("10.0.0.1")}},
+ "net2": {StaticIPs: []net.IP{net.ParseIP("10.10.0.1")}},
+ },
+ },
+ {
+ name: "multiple networks with bridge mode first should map to default net",
+ args: []string{"bridge", "net2"},
+ nsmode: Namespace{NSMode: Bridge},
+ networks: map[string]types.PerNetworkOptions{
+ defaultNetName: {},
+ "net2": {},
+ },
+ },
+ {
+ name: "conflicting network modes should error",
+ args: []string{"bridge", "host"},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "can only set extra network names, selected mode host conflicts with bridge: invalid argument",
+ },
+ {
+ name: "multiple networks empty name should error",
+ args: []string{"someName", ""},
+ nsmode: Namespace{NSMode: Bridge},
+ err: "network name cannot be empty: invalid argument",
+ },
+ {
+ name: "multiple networks on invalid mode should error",
+ args: []string{"host", "net2"},
+ nsmode: Namespace{NSMode: Host},
+ err: "cannot set multiple networks without bridge network mode, selected mode host: invalid argument",
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ got, got1, got2, err := ParseNetworkFlag(tt.args)
+ if tt.err != "" {
+ assert.EqualError(t, err, tt.err, tt.name)
+ } else {
+ assert.NoError(t, err, tt.name)
+ }
+
+ assert.Equal(t, tt.nsmode, got, tt.name)
+ assert.Equal(t, tt.networks, got1, tt.name)
+ assert.Equal(t, tt.options, got2, tt.name)
+ })
+ }
+}
diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go
index bca7b6dbe..c5a66189c 100644
--- a/pkg/specgen/pod_validate.go
+++ b/pkg/specgen/pod_validate.go
@@ -1,7 +1,6 @@
package specgen
import (
- "github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
)
@@ -19,15 +18,6 @@ func exclusivePodOptions(opt1, opt2 string) error {
// Validate verifies the input is valid
func (p *PodSpecGenerator) Validate() error {
- if rootless.IsRootless() && len(p.CNINetworks) == 0 {
- if p.StaticIP != nil {
- return ErrNoStaticIPRootless
- }
- if p.StaticMAC != nil {
- return ErrNoStaticMACRootless
- }
- }
-
// PodBasicConfig
if p.NoInfra {
if len(p.InfraCommand) > 0 {
@@ -52,11 +42,10 @@ func (p *PodSpecGenerator) Validate() error {
if p.NetNS.NSMode != Default && p.NetNS.NSMode != "" {
return errors.New("NoInfra and network modes cannot be used together")
}
- if p.StaticIP != nil {
- return exclusivePodOptions("NoInfra", "StaticIP")
- }
- if p.StaticMAC != nil {
- return exclusivePodOptions("NoInfra", "StaticMAC")
+ // Note that networks might be set when --ip or --mac was set
+ // so we need to check that no networks are set without the infra
+ if len(p.Networks) > 0 {
+ return errors.New("cannot set networks options without infra container")
}
if len(p.DNSOption) > 0 {
return exclusivePodOptions("NoInfra", "DNSOption")
@@ -78,10 +67,8 @@ func (p *PodSpecGenerator) Validate() error {
if len(p.PortMappings) > 0 {
return errors.New("PortMappings can only be used with Bridge or slirp4netns networking")
}
- if len(p.CNINetworks) > 0 {
- return errors.New("CNINetworks can only be used with Bridge mode networking")
- }
}
+
if p.NoManageResolvConf {
if len(p.DNSServer) > 0 {
return exclusivePodOptions("NoManageResolvConf", "DNSServer")
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index 948fb990c..33e8422fd 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -86,33 +86,26 @@ type PodNetworkConfig struct {
// Defaults to Bridge as root and Slirp as rootless.
// Mandatory.
NetNS Namespace `json:"netns,omitempty"`
- // StaticIP sets a static IP for the infra container. As the infra
- // container's network is used for the entire pod by default, this will
- // thus be a static IP for the whole pod.
- // Only available if NetNS is set to Bridge (the default for root).
- // As such, conflicts with NoInfra=true by proxy.
- // Optional.
- StaticIP *net.IP `json:"static_ip,omitempty"`
- // StaticMAC sets a static MAC for the infra container. As the infra
- // container's network is used for the entire pod by default, this will
- // thus be a static MAC for the entire pod.
- // Only available if NetNS is set to Bridge (the default for root).
- // As such, conflicts with NoInfra=true by proxy.
- // Optional.
- // swagger:strfmt string
- StaticMAC *types.HardwareAddr `json:"static_mac,omitempty"`
// PortMappings is a set of ports to map into the infra container.
// As, by default, containers share their network with the infra
// container, this will forward the ports to the entire pod.
// Only available if NetNS is set to Bridge or Slirp.
// Optional.
PortMappings []types.PortMapping `json:"portmappings,omitempty"`
- // CNINetworks is a list of CNI networks that the infra container will
- // join. As, by default, containers share their network with the infra
- // container, these networks will effectively be joined by the
- // entire pod.
- // Only available when NetNS is set to Bridge, the default for root.
- // Optional.
+ // Map of networks names ot ids the container should join to.
+ // You can request additional settings for each network, you can
+ // set network aliases, static ips, static mac address and the
+ // network interface name for this container on the specifc network.
+ // If the map is empty and the bridge network mode is set the container
+ // will be joined to the default network.
+ Networks map[string]types.PerNetworkOptions
+ // CNINetworks is a list of CNI networks to join the container to.
+ // If this list is empty, the default CNI network will be joined
+ // instead. If at least one entry is present, we will not join the
+ // default network (unless it is part of this list).
+ // Only available if NetNS is set to bridge.
+ // Optional.
+ // Deprecated: as of podman 4.0 use "Networks" instead.
CNINetworks []string `json:"cni_networks,omitempty"`
// NoManageResolvConf indicates that /etc/resolv.conf should not be
// managed by the pod. Instead, each container will create and manage a
@@ -203,6 +196,7 @@ type PodSpecGenerator struct {
PodCgroupConfig
PodResourceConfig
PodStorageConfig
+ PodSecurityConfig
InfraContainerSpec *SpecGenerator `json:"-"`
}
@@ -217,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/specgen/specgen.go b/pkg/specgen/specgen.go
index 0e257ad4c..5989456c9 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -152,6 +152,9 @@ type ContainerBasicConfig struct {
// Conflicts with UtsNS if UtsNS is not set to private.
// Optional.
Hostname string `json:"hostname,omitempty"`
+ // HostUses is a list of host usernames or UIDs to add to the container
+ // /etc/passwd file
+ HostUsers []string `json:"hostusers,omitempty"`
// Sysctl sets kernel parameters for the container
Sysctl map[string]string `json:"sysctl,omitempty"`
// Remove indicates if the container should be removed once it has been started
@@ -201,6 +204,8 @@ type ContainerBasicConfig struct {
// UnsetEnvAll unsets all default environment variables from the image or from buildin
// Optional.
UnsetEnvAll bool `json:"unsetenvall,omitempty"`
+ // Passwd is a container run option that determines if we are validating users/groups before running the container
+ Passwd *bool `json:"manage_password,omitempty"`
}
// ContainerStorageConfig contains information on the storage configuration of a
@@ -394,26 +399,10 @@ type ContainerCgroupConfig struct {
// ContainerNetworkConfig contains information on a container's network
// configuration.
type ContainerNetworkConfig struct {
- // Aliases are a list of network-scoped aliases for container
- // Optional
- Aliases map[string][]string `json:"aliases"`
// NetNS is the configuration to use for the container's network
// namespace.
// Mandatory.
NetNS Namespace `json:"netns,omitempty"`
- // StaticIP is the a IPv4 address of the container.
- // Only available if NetNS is set to Bridge.
- // Optional.
- StaticIP *net.IP `json:"static_ip,omitempty"`
- // StaticIPv6 is a static IPv6 address to set in the container.
- // Only available if NetNS is set to Bridge.
- // Optional.
- StaticIPv6 *net.IP `json:"static_ipv6,omitempty"`
- // StaticMAC is a static MAC address to set in the container.
- // Only available if NetNS is set to bridge.
- // Optional.
- // swagger:strfmt string
- StaticMAC *nettypes.HardwareAddr `json:"static_mac,omitempty"`
// PortBindings is a set of ports to map into the container.
// Only available if NetNS is set to bridge or slirp.
// Optional.
@@ -434,12 +423,20 @@ type ContainerNetworkConfig struct {
// PublishExposedPorts is set.
// Optional.
Expose map[uint16]string `json:"expose,omitempty"`
+ // Map of networks names ot ids the container should join to.
+ // You can request additional settings for each network, you can
+ // set network aliases, static ips, static mac address and the
+ // network interface name for this container on the specifc network.
+ // If the map is empty and the bridge network mode is set the container
+ // will be joined to the default network.
+ Networks map[string]nettypes.PerNetworkOptions
// CNINetworks is a list of CNI networks to join the container to.
// If this list is empty, the default CNI network will be joined
// instead. If at least one entry is present, we will not join the
// default network (unless it is part of this list).
// Only available if NetNS is set to bridge.
// Optional.
+ // Deprecated: as of podman 4.0 use "Networks" instead.
CNINetworks []string `json:"cni_networks,omitempty"`
// UseImageResolvConf indicates that resolv.conf should not be managed
// by Podman, but instead sourced from the image.
diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go
index 5e4bd2f65..8e43cc50e 100644
--- a/pkg/specgenutil/specgen.go
+++ b/pkg/specgenutil/specgen.go
@@ -11,7 +11,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v3/cmd/podman/parse"
"github.com/containers/podman/v3/libpod/define"
- "github.com/containers/podman/v3/libpod/network/types"
ann "github.com/containers/podman/v3/pkg/annotations"
"github.com/containers/podman/v3/pkg/domain/entities"
envLib "github.com/containers/podman/v3/pkg/env"
@@ -164,14 +163,6 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOption
hasLimits = true
}
}
- if m := c.KernelMemory; len(m) > 0 {
- mk, err := units.RAMInBytes(m)
- if err != nil {
- return nil, errors.Wrapf(err, "invalid value for kernel-memory")
- }
- memory.Kernel = &mk
- hasLimits = true
- }
if c.MemorySwappiness >= 0 {
swappiness := uint64(c.MemorySwappiness)
memory.Swappiness = &swappiness
@@ -434,19 +425,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
}
if c.Net != nil {
- s.CNINetworks = c.Net.CNINetworks
- }
-
- // Network aliases
- if c.Net != nil {
- if len(c.Net.Aliases) > 0 {
- // build a map of aliases where key=cniName
- aliases := make(map[string][]string, len(s.CNINetworks))
- for _, cniNetwork := range s.CNINetworks {
- aliases[cniNetwork] = c.Net.Aliases
- }
- s.Aliases = aliases
- }
+ s.Networks = c.Net.Networks
}
if c.Net != nil {
@@ -455,12 +434,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
s.DNSServers = c.Net.DNSServers
s.DNSSearch = c.Net.DNSSearch
s.DNSOptions = c.Net.DNSOptions
- s.StaticIP = c.Net.StaticIP
- // type cast to types.HardwareAddr
- s.StaticMAC = (*types.HardwareAddr)(c.Net.StaticMAC)
s.NetworkOptions = c.Net.NetworkOptions
s.UseImageHosts = c.Net.NoHosts
}
+ s.HostUsers = c.HostUsers
s.ImageVolumeMode = c.ImageVolume
if s.ImageVolumeMode == "bind" {
s.ImageVolumeMode = "anonymous"
@@ -714,6 +691,9 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
// Initcontainers
s.InitContainerType = c.InitContainerType
+
+ t := true
+ s.Passwd = &t
return nil
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 208d815d9..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
@@ -723,3 +723,11 @@ func SocketPath() (string, error) {
// Glue the socket path together
return filepath.Join(xdg, "podman", "podman.sock"), nil
}
+
+func LookupUser(name string) (*user.User, error) {
+ // Assume UID look up first, if it fails lookup by username
+ if u, err := user.LookupId(name); err == nil {
+ return u, err
+ }
+ return user.Lookup(name)
+}