summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/libpod/manifests.go2
-rw-r--r--pkg/api/server/register_networks.go2
-rw-r--r--pkg/bindings/images/build.go2
-rw-r--r--pkg/bindings/manifests/manifests.go115
-rw-r--r--pkg/bindings/test/common_test.go8
-rw-r--r--pkg/bindings/test/manifests_test.go4
-rw-r--r--pkg/checkpoint/checkpoint_restore.go7
-rw-r--r--pkg/machine/config.go9
-rw-r--r--pkg/machine/connection.go25
-rw-r--r--pkg/machine/ignition.go85
-rw-r--r--pkg/machine/qemu/claim_darwin.go63
-rw-r--r--pkg/machine/qemu/claim_unsupported.go20
-rw-r--r--pkg/machine/qemu/config.go2
-rw-r--r--pkg/machine/qemu/machine.go283
-rw-r--r--pkg/machine/wsl/machine.go80
15 files changed, 574 insertions, 133 deletions
diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go
index 250736579..ad662f32c 100644
--- a/pkg/api/handlers/libpod/manifests.go
+++ b/pkg/api/handlers/libpod/manifests.go
@@ -401,7 +401,7 @@ func ManifestModify(w http.ResponseWriter, r *http.Request) {
case len(report.Errors) > 0 && len(report.Images) > 0:
statusCode = http.StatusConflict
case len(report.Errors) > 0:
- statusCode = http.StatusInternalServerError
+ statusCode = http.StatusBadRequest
}
utils.WriteResponse(w, statusCode, report)
}
diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go
index baa1fe6fb..4466c938f 100644
--- a/pkg/api/server/register_networks.go
+++ b/pkg/api/server/register_networks.go
@@ -320,6 +320,8 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// $ref: "#/responses/NetworkCreateReport"
// 400:
// $ref: "#/responses/BadParamError"
+ // 409:
+ // $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost)
diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go
index a363f2c6e..c508cb767 100644
--- a/pkg/bindings/images/build.go
+++ b/pkg/bindings/images/build.go
@@ -352,11 +352,13 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
}
c = tmpFile.Name()
}
+ c = filepath.Clean(c)
cfDir := filepath.Dir(c)
if absDir, err := filepath.EvalSymlinks(cfDir); err == nil {
name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator)))
c = filepath.Join(absDir, name)
}
+
containerfile, err := filepath.Abs(c)
if err != nil {
logrus.Errorf("Cannot find absolute path of %v: %v", c, err)
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index 458cb913a..18798e615 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -2,10 +2,8 @@ package manifests
import (
"context"
- "errors"
- "fmt"
+ "io/ioutil"
"net/http"
- "net/url"
"strconv"
"strings"
@@ -14,8 +12,10 @@ import (
"github.com/containers/podman/v4/pkg/api/handlers"
"github.com/containers/podman/v4/pkg/bindings"
"github.com/containers/podman/v4/pkg/bindings/images"
- "github.com/containers/podman/v4/version"
+ "github.com/containers/podman/v4/pkg/domain/entities"
+ "github.com/containers/podman/v4/pkg/errorhandling"
jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
)
// Create creates a manifest for the given name. Optional images to be associated with
@@ -91,74 +91,26 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error)
options = new(AddOptions)
}
- if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
- optionsv4 := ModifyOptions{
- All: options.All,
- Annotations: options.Annotation,
- Arch: options.Arch,
- Features: options.Features,
- Images: options.Images,
- OS: options.OS,
- OSFeatures: nil,
- OSVersion: options.OSVersion,
- Variant: options.Variant,
- }
- optionsv4.WithOperation("update")
- return Modify(ctx, name, options.Images, &optionsv4)
- }
-
- // API Version < 4.0.0
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return "", err
- }
- opts, err := jsoniter.MarshalToString(options)
- if err != nil {
- return "", err
- }
- reader := strings.NewReader(opts)
-
- headers := make(http.Header)
- v := version.APIVersion[version.Libpod][version.MinimalAPI]
- headers.Add("API-Version",
- fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch))
- response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/manifests/%s/add", nil, headers, name)
- if err != nil {
- return "", err
+ optionsv4 := ModifyOptions{
+ All: options.All,
+ Annotations: options.Annotation,
+ Arch: options.Arch,
+ Features: options.Features,
+ Images: options.Images,
+ OS: options.OS,
+ OSFeatures: nil,
+ OSVersion: options.OSVersion,
+ Variant: options.Variant,
}
- defer response.Body.Close()
-
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ optionsv4.WithOperation("update")
+ return Modify(ctx, name, options.Images, &optionsv4)
}
// Remove deletes a manifest entry from a manifest list. Both name and the digest to be
// removed are mandatory inputs. The ID of the new manifest list is returned as a string.
func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, error) {
- if bindings.ServiceVersion(ctx).GTE(semver.MustParse("4.0.0")) {
- optionsv4 := new(ModifyOptions).WithOperation("remove")
- return Modify(ctx, name, []string{digest}, optionsv4)
- }
-
- // API Version < 4.0.0
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return "", err
- }
-
- headers := http.Header{}
- headers.Add("API-Version", "3.4.0")
-
- params := url.Values{}
- params.Set("digest", digest)
- response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", params, headers, name)
- if err != nil {
- return "", err
- }
- defer response.Body.Close()
-
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ optionsv4 := new(ModifyOptions).WithOperation("remove")
+ return Modify(ctx, name, []string{digest}, optionsv4)
}
// Push takes a manifest list and pushes to a destination. If the destination is not specified,
@@ -229,8 +181,35 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp
}
defer response.Body.Close()
- var idr handlers.IDResponse
- return idr.ID, response.Process(&idr)
+ data, err := ioutil.ReadAll(response.Body)
+ if err != nil {
+ return "", errors.Wrap(err, "unable to process API response")
+ }
+
+ if response.IsSuccess() || response.IsRedirection() {
+ var report entities.ManifestModifyReport
+ if err = jsoniter.Unmarshal(data, &report); err != nil {
+ return "", errors.Wrap(err, "unable to decode API response")
+ }
+
+ err = errorhandling.JoinErrors(report.Errors)
+ if err != nil {
+ errModel := errorhandling.ErrorModel{
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ ResponseCode: response.StatusCode,
+ }
+ return report.ID, &errModel
+ }
+ return report.ID, nil
+ }
+ errModel := errorhandling.ErrorModel{
+ ResponseCode: response.StatusCode,
+ }
+ if err = jsoniter.Unmarshal(data, &errModel); err != nil {
+ return "", errors.Wrap(err, "unable to decode API response")
+ }
+ return "", &errModel
}
// Annotate modifies the given manifest list using options and the optional list of images
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index f51e5f404..b75588251 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"strings"
+ "testing"
"time"
"github.com/containers/podman/v4/libpod/define"
@@ -151,7 +152,12 @@ func createTempDirInTempDir() (string, error) {
}
func (b *bindingTest) startAPIService() *gexec.Session {
- cmd := []string{"--log-level=debug", "system", "service", "--timeout=0", b.sock}
+ logLevel := "debug"
+ if testing.Verbose() {
+ logLevel = "trace"
+ }
+
+ cmd := []string{"--log-level=" + logLevel, "system", "service", "--timeout=0", b.sock}
session := b.runPodman(cmd)
sock := strings.TrimPrefix(b.sock, "unix://")
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 64becda43..895e1a29d 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -87,7 +87,6 @@ var _ = Describe("podman manifest", func() {
list, err := manifests.Inspect(bt.conn, id, nil)
Expect(err).ToNot(HaveOccurred())
-
Expect(len(list.Manifests)).To(BeNumerically("==", 1))
// add bogus name to existing list should fail
@@ -96,7 +95,7 @@ var _ = Describe("podman manifest", func() {
Expect(err).To(HaveOccurred())
code, _ = bindings.CheckResponseCode(err)
- Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ Expect(code).To(BeNumerically("==", http.StatusBadRequest))
})
It("remove digest", func() {
@@ -129,7 +128,6 @@ var _ = Describe("podman manifest", func() {
// removal on good manifest with good digest should work
data, err = manifests.Inspect(bt.conn, id, nil)
Expect(err).ToNot(HaveOccurred())
-
Expect(data.Manifests).Should(BeEmpty())
})
diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go
index 1ebd6a455..270b5b6c4 100644
--- a/pkg/checkpoint/checkpoint_restore.go
+++ b/pkg/checkpoint/checkpoint_restore.go
@@ -140,6 +140,13 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt
return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod)
}
ctrConfig.NetNsCtr = infraContainer.ID()
+ for net, opts := range ctrConfig.Networks {
+ opts.StaticIPs = nil
+ opts.StaticMAC = nil
+ ctrConfig.Networks[net] = opts
+ }
+ ctrConfig.StaticIP = nil
+ ctrConfig.StaticMAC = nil
}
if ctrConfig.PIDNsCtr != "" {
diff --git a/pkg/machine/config.go b/pkg/machine/config.go
index 97237f5e5..efb1eda15 100644
--- a/pkg/machine/config.go
+++ b/pkg/machine/config.go
@@ -27,6 +27,7 @@ type InitOptions struct {
URI url.URL
Username string
ReExec bool
+ Rootful bool
}
type QemuMachineStatus = string
@@ -35,7 +36,8 @@ const (
// Running indicates the qemu vm is running
Running QemuMachineStatus = "running"
// Stopped indicates the vm has stopped
- Stopped QemuMachineStatus = "stopped"
+ Stopped QemuMachineStatus = "stopped"
+ DefaultMachineName string = "podman-machine-default"
)
type Provider interface {
@@ -89,6 +91,10 @@ type ListResponse struct {
IdentityPath string
}
+type SetOptions struct {
+ Rootful bool
+}
+
type SSHOptions struct {
Username string
Args []string
@@ -107,6 +113,7 @@ type RemoveOptions struct {
type VM interface {
Init(opts InitOptions) (bool, error)
Remove(name string, opts RemoveOptions) (string, func() error, error)
+ Set(name string, opts SetOptions) error
SSH(name string, opts SSHOptions) error
Start(name string, opts StartOptions) error
Stop(name string, opts StopOptions) error
diff --git a/pkg/machine/connection.go b/pkg/machine/connection.go
index d28ffcef1..841b2afa6 100644
--- a/pkg/machine/connection.go
+++ b/pkg/machine/connection.go
@@ -39,6 +39,31 @@ func AddConnection(uri fmt.Stringer, name, identity string, isDefault bool) erro
return cfg.Write()
}
+func AnyConnectionDefault(name ...string) (bool, error) {
+ cfg, err := config.ReadCustomConfig()
+ if err != nil {
+ return false, err
+ }
+ for _, n := range name {
+ if n == cfg.Engine.ActiveService {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func ChangeDefault(name string) error {
+ cfg, err := config.ReadCustomConfig()
+ if err != nil {
+ return err
+ }
+
+ cfg.Engine.ActiveService = name
+
+ return cfg.Write()
+}
+
func RemoveConnection(name string) error {
cfg, err := config.ReadCustomConfig()
if err != nil {
diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go
index 206c9144f..47b1836f0 100644
--- a/pkg/machine/ignition.go
+++ b/pkg/machine/ignition.go
@@ -145,7 +145,42 @@ ExecStartPost=/bin/touch /var/lib/%N.stamp
[Install]
WantedBy=default.target
- `
+`
+ // This service gets environment variables that are provided
+ // through qemu fw_cfg and then sets them into systemd/system.conf.d,
+ // profile.d and environment.d files
+ //
+ // Currently, it is used for propagating
+ // proxy settings e.g. HTTP_PROXY and others, on a start avoiding
+ // a need of re-creating/re-initiating a VM
+ envset := `[Unit]
+Description=Environment setter from QEMU FW_CFG
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+Environment=FWCFGRAW=/sys/firmware/qemu_fw_cfg/by_name/opt/com.coreos/environment/raw
+Environment=SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf
+Environment=ENVD_CONF=/etc/environment.d/default-env.conf
+Environment=PROFILE_CONF=/etc/profile.d/default-env.sh
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} &&\
+ echo "[Manager]\n#Got from QEMU FW_CFG\nDefaultEnvironment=$(/usr/bin/base64 -d ${FWCFGRAW} | sed -e "s+|+ +g")\n" > ${SYSTEMD_CONF} ||\
+ echo "[Manager]\n#Got nothing from QEMU FW_CFG\n#DefaultEnvironment=\n" > ${SYSTEMD_CONF}'
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
+ echo "#Got from QEMU FW_CFG"> ${ENVD_CONF};\
+ IFS="|";\
+ for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
+ echo "$iprxy" >> ${ENVD_CONF}; done ) || \
+ echo "#Got nothing from QEMU FW_CFG"> ${ENVD_CONF}'
+ExecStart=/usr/bin/bash -c '/usr/bin/test -f ${FWCFGRAW} && (\
+ echo "#Got from QEMU FW_CFG"> ${PROFILE_CONF};\
+ IFS="|";\
+ for iprxy in $(/usr/bin/base64 -d ${FWCFGRAW}); do\
+ echo "export $iprxy" >> ${PROFILE_CONF}; done ) || \
+ echo "#Got nothing from QEMU FW_CFG"> ${PROFILE_CONF}'
+ExecStartPost=/usr/bin/systemctl daemon-reload
+[Install]
+WantedBy=sysinit.target
+`
_ = ready
ignSystemd := Systemd{
Units: []Unit{
@@ -173,6 +208,11 @@ WantedBy=default.target
Name: "remove-moby.service",
Contents: &deMoby,
},
+ {
+ Enabled: boolToPtr(true),
+ Name: "envset-fwcfg.service",
+ Contents: &envset,
+ },
}}
ignConfig := Config{
Ignition: ignVersion,
@@ -226,6 +266,25 @@ func getDirs(usrName string) []Directory {
DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
})
+ // The directory is used by envset-fwcfg.service
+ // for propagating environment variables that got
+ // from a host
+ dirs = append(dirs, Directory{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/systemd/system.conf.d",
+ User: getNodeUsr("root"),
+ },
+ DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
+ }, Directory{
+ Node: Node{
+ Group: getNodeGrp("root"),
+ Path: "/etc/environment.d",
+ User: getNodeUsr("root"),
+ },
+ DirectoryEmbedded1: DirectoryEmbedded1{Mode: intToPtr(0755)},
+ })
+
return dirs
}
@@ -363,24 +422,6 @@ Delegate=memory pids cpu io
},
})
- setProxyOpts := getProxyVariables()
- if setProxyOpts != "" {
- files = append(files, File{
- Node: Node{
- Group: getNodeGrp("root"),
- Path: "/etc/profile.d/proxy-opts.sh",
- User: getNodeUsr("root"),
- },
- FileEmbedded1: FileEmbedded1{
- Append: nil,
- Contents: Resource{
- Source: encodeDataURLPtr(setProxyOpts),
- },
- Mode: intToPtr(0644),
- },
- })
- }
-
setDockerHost := `export DOCKER_HOST="unix://$(podman info -f "{{.Host.RemoteSocket.Path}}")"
`
@@ -506,11 +547,11 @@ func prepareCertFile(path string, name string) (File, error) {
return file, nil
}
-func getProxyVariables() string {
- proxyOpts := ""
+func GetProxyVariables() map[string]string {
+ proxyOpts := make(map[string]string)
for _, variable := range config.ProxyEnv {
if value, ok := os.LookupEnv(variable); ok {
- proxyOpts += fmt.Sprintf("\n export %s=%s", variable, value)
+ proxyOpts[variable] = value
}
}
return proxyOpts
diff --git a/pkg/machine/qemu/claim_darwin.go b/pkg/machine/qemu/claim_darwin.go
new file mode 100644
index 000000000..66aed9ad8
--- /dev/null
+++ b/pkg/machine/qemu/claim_darwin.go
@@ -0,0 +1,63 @@
+package qemu
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/user"
+ "path/filepath"
+ "time"
+)
+
+func dockerClaimSupported() bool {
+ return true
+}
+
+func dockerClaimHelperInstalled() bool {
+ u, err := user.Current()
+ if err != nil {
+ return false
+ }
+
+ labelName := fmt.Sprintf("com.github.containers.podman.helper-%s", u.Username)
+ fileName := filepath.Join("/Library", "LaunchDaemons", labelName+".plist")
+ info, err := os.Stat(fileName)
+ return err == nil && info.Mode().IsRegular()
+}
+
+func claimDockerSock() bool {
+ u, err := user.Current()
+ if err != nil {
+ return false
+ }
+
+ helperSock := fmt.Sprintf("/var/run/podman-helper-%s.socket", u.Username)
+ con, err := net.DialTimeout("unix", helperSock, time.Second*5)
+ if err != nil {
+ return false
+ }
+ _ = con.SetWriteDeadline(time.Now().Add(time.Second * 5))
+ _, err = fmt.Fprintln(con, "GO")
+ if err != nil {
+ return false
+ }
+ _ = con.SetReadDeadline(time.Now().Add(time.Second * 5))
+ read, err := ioutil.ReadAll(con)
+
+ return err == nil && string(read) == "OK"
+}
+
+func findClaimHelper() string {
+ exe, err := os.Executable()
+ if err != nil {
+ return ""
+ }
+
+ exe, err = filepath.EvalSymlinks(exe)
+ if err != nil {
+ return ""
+ }
+
+ return filepath.Join(filepath.Dir(exe), "podman-mac-helper")
+}
diff --git a/pkg/machine/qemu/claim_unsupported.go b/pkg/machine/qemu/claim_unsupported.go
new file mode 100644
index 000000000..e0b3dd3d3
--- /dev/null
+++ b/pkg/machine/qemu/claim_unsupported.go
@@ -0,0 +1,20 @@
+//go:build !darwin && !windows
+// +build !darwin,!windows
+
+package qemu
+
+func dockerClaimHelperInstalled() bool {
+ return false
+}
+
+func claimDockerSock() bool {
+ return false
+}
+
+func dockerClaimSupported() bool {
+ return false
+}
+
+func findClaimHelper() string {
+ return ""
+}
diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go
index e76509bb1..c619b7dd4 100644
--- a/pkg/machine/qemu/config.go
+++ b/pkg/machine/qemu/config.go
@@ -33,6 +33,8 @@ type MachineVM struct {
QMPMonitor Monitor
// RemoteUsername of the vm user
RemoteUsername string
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
}
type Mount struct {
diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go
index eb7b35ece..9beec2173 100644
--- a/pkg/machine/qemu/machine.go
+++ b/pkg/machine/qemu/machine.go
@@ -1,13 +1,19 @@
+//go:build (amd64 && !windows) || (arm64 && !windows)
// +build amd64,!windows arm64,!windows
package qemu
import (
"bufio"
+ "context"
+ "encoding/base64"
"encoding/json"
"fmt"
+ "io/fs"
"io/ioutil"
"net"
+ "net/http"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -37,8 +43,21 @@ func GetQemuProvider() machine.Provider {
}
const (
- VolumeTypeVirtfs = "virtfs"
- MountType9p = "9p"
+ VolumeTypeVirtfs = "virtfs"
+ MountType9p = "9p"
+ dockerSock = "/var/run/docker.sock"
+ dockerConnectTimeout = 5 * time.Second
+ apiUpTimeout = 20 * time.Second
+)
+
+type apiForwardingState int
+
+const (
+ noForwarding apiForwardingState = iota
+ claimUnsupported
+ notInstalled
+ machineLocal
+ dockerGlobal
)
// NewMachine initializes an instance of a virtual machine based on the qemu
@@ -123,6 +142,20 @@ func (p *Provider) LoadVMByName(name string) (machine.VM, error) {
return nil, err
}
err = json.Unmarshal(b, vm)
+
+ // It is here for providing the ability to propagate
+ // proxy settings (e.g. HTTP_PROXY and others) on a start
+ // and avoid a need of re-creating/re-initiating a VM
+ if proxyOpts := machine.GetProxyVariables(); len(proxyOpts) > 0 {
+ proxyStr := "name=opt/com.coreos/environment,string="
+ var proxies string
+ for k, v := range proxyOpts {
+ proxies = fmt.Sprintf("%s%s=\"%s\"|", proxies, k, v)
+ }
+ proxyStr = fmt.Sprintf("%s%s", proxyStr, base64.StdEncoding.EncodeToString([]byte(proxies)))
+ vm.CmdLine = append(vm.CmdLine, "-fw_cfg", proxyStr)
+ }
+
logrus.Debug(vm.CmdLine)
return vm, err
}
@@ -134,14 +167,8 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
key string
)
sshDir := filepath.Join(homedir.Get(), ".ssh")
- // GetConfDir creates the directory so no need to check for
- // its existence
- vmConfigDir, err := machine.GetConfDir(vmtype)
- if err != nil {
- return false, err
- }
- jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
v.IdentityPath = filepath.Join(sshDir, v.Name)
+ v.Rootful = opts.Rootful
switch opts.ImagePath {
case "testing", "next", "stable", "":
@@ -224,29 +251,33 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
// This kind of stinks but no other way around this r/n
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 false, err
+ uriRoot := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/podman/podman.sock", strconv.Itoa(v.Port), "root")
+ identity := filepath.Join(sshDir, v.Name)
+
+ uris := []url.URL{uri, uriRoot}
+ names := []string{v.Name, v.Name + "-root"}
+
+ // The first connection defined when connections is empty will become the default
+ // regardless of IsDefault, so order according to rootful
+ if opts.Rootful {
+ uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
}
- 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 false, err
+ for i := 0; i < 2; i++ {
+ if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); err != nil {
+ return false, err
+ }
}
} else {
fmt.Println("An ignition path was provided. No SSH connection was added to Podman")
}
// Write the JSON file
- b, err := json.MarshalIndent(v, "", " ")
- if err != nil {
- return false, err
- }
- if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
- return false, err
- }
+ v.writeConfig()
// User has provided ignition file so keygen
// will be skipped.
if len(opts.IgnitionPath) < 1 {
+ var err error
key, err = machine.CreateSSHKeys(v.IdentityPath)
if err != nil {
return false, err
@@ -293,6 +324,30 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
return err == nil, err
}
+func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
+ if v.Rootful == opts.Rootful {
+ return nil
+ }
+
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if opts.Rootful {
+ newDefault += "-root"
+ }
+ if err := machine.ChangeDefault(newDefault); err != nil {
+ return err
+ }
+ }
+
+ v.Rootful = opts.Rootful
+ return v.writeConfig()
+}
+
// Start executes the qemu command line and forks it
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
var (
@@ -302,7 +357,8 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
wait time.Duration = time.Millisecond * 500
)
- if err := v.startHostNetworking(); err != nil {
+ forwardSock, forwardState, err := v.startHostNetworking()
+ if err != nil {
return errors.Errorf("unable to start host networking: %q", err)
}
@@ -423,6 +479,9 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return fmt.Errorf("unknown mount type: %s", mount.Type)
}
}
+
+ waitAPIAndPrintInfo(forwardState, forwardSock, v.Rootful, v.Name)
+
return nil
}
@@ -853,19 +912,19 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) {
// startHostNetworking runs a binary on the host system that allows users
// to setup port forwarding to the podman virtual machine
-func (v *MachineVM) startHostNetworking() error {
+func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) {
cfg, err := config.Default()
if err != nil {
- return err
+ return "", noForwarding, err
}
binary, err := cfg.FindHelperBinary(machine.ForwarderBinaryName, false)
if err != nil {
- return err
+ return "", noForwarding, err
}
qemuSocket, pidFile, err := v.getSocketandPid()
if err != nil {
- return err
+ return "", noForwarding, err
}
attr := new(os.ProcAttr)
// Pass on stdin, stdout, stderr
@@ -875,12 +934,82 @@ func (v *MachineVM) startHostNetworking() error {
cmd = append(cmd, []string{"-listen-qemu", fmt.Sprintf("unix://%s", qemuSocket), "-pid-file", pidFile}...)
// Add the ssh port
cmd = append(cmd, []string{"-ssh-port", fmt.Sprintf("%d", v.Port)}...)
+
+ cmd, forwardSock, state := v.setupAPIForwarding(cmd)
+
if logrus.GetLevel() == logrus.DebugLevel {
cmd = append(cmd, "--debug")
fmt.Println(cmd)
}
_, err = os.StartProcess(cmd[0], cmd, attr)
- return err
+ return forwardSock, state, err
+}
+
+func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) {
+ socket, err := v.getForwardSocketPath()
+
+ if err != nil {
+ return cmd, "", noForwarding
+ }
+
+ destSock := "/run/user/1000/podman/podman.sock"
+ forwardUser := "core"
+
+ if v.Rootful {
+ destSock = "/run/podman/podman.sock"
+ forwardUser = "root"
+ }
+
+ cmd = append(cmd, []string{"-forward-sock", socket}...)
+ cmd = append(cmd, []string{"-forward-dest", destSock}...)
+ cmd = append(cmd, []string{"-forward-user", forwardUser}...)
+ cmd = append(cmd, []string{"-forward-identity", v.IdentityPath}...)
+ link := filepath.Join(filepath.Dir(filepath.Dir(socket)), "podman.sock")
+
+ // The linking pattern is /var/run/docker.sock -> user global sock (link) -> machine sock (socket)
+ // This allows the helper to only have to maintain one constant target to the user, which can be
+ // repositioned without updating docker.sock.
+ if !dockerClaimSupported() {
+ return cmd, socket, claimUnsupported
+ }
+
+ if !dockerClaimHelperInstalled() {
+ return cmd, socket, notInstalled
+ }
+
+ if !alreadyLinked(socket, link) {
+ if checkSockInUse(link) {
+ return cmd, socket, machineLocal
+ }
+
+ _ = os.Remove(link)
+ if err = os.Symlink(socket, link); err != nil {
+ logrus.Warnf("could not create user global API forwarding link: %s", err.Error())
+ return cmd, socket, machineLocal
+ }
+ }
+
+ if !alreadyLinked(link, dockerSock) {
+ if checkSockInUse(dockerSock) {
+ return cmd, socket, machineLocal
+ }
+
+ if !claimDockerSock() {
+ logrus.Warn("podman helper is installed, but was not able to claim the global docker sock")
+ return cmd, socket, machineLocal
+ }
+ }
+
+ return cmd, dockerSock, dockerGlobal
+}
+
+func (v *MachineVM) getForwardSocketPath() (string, error) {
+ path, err := machine.GetDataDir(v.Name)
+ if err != nil {
+ logrus.Errorf("Error resolving data dir: %s", err.Error())
+ return "", nil
+ }
+ return filepath.Join(path, "podman.sock"), nil
}
func (v *MachineVM) getSocketandPid() (string, string, error) {
@@ -896,3 +1025,103 @@ func (v *MachineVM) getSocketandPid() (string, string, error) {
qemuSocket := filepath.Join(socketDir, fmt.Sprintf("qemu_%s.sock", v.Name))
return qemuSocket, pidFile, nil
}
+
+func checkSockInUse(sock string) bool {
+ if info, err := os.Stat(sock); err == nil && info.Mode()&fs.ModeSocket == fs.ModeSocket {
+ _, err = net.DialTimeout("unix", dockerSock, dockerConnectTimeout)
+ return err == nil
+ }
+
+ return false
+}
+
+func alreadyLinked(target string, link string) bool {
+ read, err := os.Readlink(link)
+ return err == nil && read == target
+}
+
+func waitAndPingAPI(sock string) {
+ client := http.Client{
+ Transport: &http.Transport{
+ DialContext: func(context.Context, string, string) (net.Conn, error) {
+ con, err := net.DialTimeout("unix", sock, apiUpTimeout)
+ if err == nil {
+ con.SetDeadline(time.Now().Add(apiUpTimeout))
+ }
+ return con, err
+ },
+ },
+ }
+
+ resp, err := client.Get("http://host/_ping")
+ if err == nil {
+ defer resp.Body.Close()
+ }
+ if err != nil || resp.StatusCode != 200 {
+ logrus.Warn("API socket failed ping test")
+ }
+}
+
+func waitAPIAndPrintInfo(forwardState apiForwardingState, forwardSock string, rootFul bool, name string) {
+ if forwardState != noForwarding {
+ waitAndPingAPI(forwardSock)
+ if !rootFul {
+ fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
+ fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
+ fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
+
+ suffix := ""
+ if name != machine.DefaultMachineName {
+ suffix = " " + name
+ }
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ }
+
+ fmt.Printf("API forwarding listening on: %s\n", forwardSock)
+ if forwardState == dockerGlobal {
+ fmt.Printf("Docker API clients default to this address. You do not need to set DOCKER_HOST.\n\n")
+ } else {
+ stillString := "still "
+ switch forwardState {
+ case notInstalled:
+ fmt.Printf("\nThe system helper service is not installed; the default Docker API socket\n")
+ fmt.Printf("address can't be used by podman. ")
+ if helper := findClaimHelper(); len(helper) > 0 {
+ fmt.Printf("If you would like to install it run the\nfollowing command:\n")
+ fmt.Printf("\n\tsudo %s install\n\n", helper)
+ }
+ case machineLocal:
+ fmt.Printf("\nAnother process was listening on the default Docker API socket address.\n")
+ case claimUnsupported:
+ fallthrough
+ default:
+ stillString = ""
+ }
+
+ fmt.Printf("You can %sconnect Docker API clients by setting DOCKER_HOST using the\n", stillString)
+ fmt.Printf("following command in your terminal session:\n")
+ fmt.Printf("\n\texport DOCKER_HOST='unix://%s'\n\n", forwardSock)
+ }
+ }
+}
+
+func (v *MachineVM) writeConfig() error {
+ // GetConfDir creates the directory so no need to check for
+ // its existence
+ vmConfigDir, err := machine.GetConfDir(vmtype)
+ if err != nil {
+ return err
+ }
+
+ jsonFile := filepath.Join(vmConfigDir, v.Name) + ".json"
+ // Write the JSON file
+ b, err := json.MarshalIndent(v, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(jsonFile, b, 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go
index 3edf3ddf6..5b0c757f0 100644
--- a/pkg/machine/wsl/machine.go
+++ b/pkg/machine/wsl/machine.go
@@ -1,3 +1,4 @@
+//go:build windows
// +build windows
package wsl
@@ -8,6 +9,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net/url"
"os"
"os/exec"
"path/filepath"
@@ -35,9 +37,6 @@ const (
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]
@@ -162,6 +161,8 @@ type MachineVM struct {
Port int
// RemoteUsername of the vm user
RemoteUsername string
+ // Whether this machine should run in a rootful or rootless manner
+ Rootful bool
}
type ExitCodeError struct {
@@ -227,12 +228,13 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) {
homeDir := homedir.Get()
sshDir := filepath.Join(homeDir, ".ssh")
v.IdentityPath = filepath.Join(sshDir, v.Name)
+ v.Rootful = opts.Rootful
if err := downloadDistro(v, opts); err != nil {
return false, err
}
- if err := writeJSON(v); err != nil {
+ if err := v.writeConfig(); err != nil {
return false, err
}
@@ -282,7 +284,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error {
return machine.DownloadImage(dd)
}
-func writeJSON(v *MachineVM) error {
+func (v *MachineVM) writeConfig() error {
vmConfigDir, err := machine.GetConfDir(vmtype)
if err != nil {
return err
@@ -302,14 +304,26 @@ func writeJSON(v *MachineVM) error {
}
func setupConnections(v *MachineVM, opts machine.InitOptions, sshDir string) error {
+ uri := machine.SSHRemoteConnection.MakeSSHURL("localhost", "/run/user/1000/podman/podman.sock", strconv.Itoa(v.Port), v.RemoteUsername)
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
+ identity := filepath.Join(sshDir, v.Name)
+
+ uris := []url.URL{uri, uriRoot}
+ names := []string{v.Name, v.Name + "-root"}
+
+ // The first connection defined when connections is empty will become the default
+ // regardless of IsDefault, so order according to rootful
+ if opts.Rootful {
+ uris[0], names[0], uris[1], names[1] = uris[1], names[1], uris[0], names[0]
+ }
+
+ for i := 0; i < 2; i++ {
+ if err := machine.AddConnection(&uris[i], names[i], identity, opts.IsDefault && i == 0); 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)
+ return nil
}
func provisionWSLDist(v *MachineVM) (string, error) {
@@ -335,6 +349,16 @@ func provisionWSLDist(v *MachineVM) (string, error) {
return "", errors.Wrap(err, "package upgrade on guest OS failed")
}
+ fmt.Println("Enabling Copr")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil {
+ return "", errors.Wrap(err, "enabling copr failed")
+ }
+
+ fmt.Println("Enabling podman4 repo")
+ if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil {
+ return "", errors.Wrap(err, "enabling copr 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")
@@ -704,6 +728,30 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error {
return cmd.Run()
}
+func (v *MachineVM) Set(name string, opts machine.SetOptions) error {
+ if v.Rootful == opts.Rootful {
+ return nil
+ }
+
+ changeCon, err := machine.AnyConnectionDefault(v.Name, v.Name+"-root")
+ if err != nil {
+ return err
+ }
+
+ if changeCon {
+ newDefault := v.Name
+ if opts.Rootful {
+ newDefault += "-root"
+ }
+ if err := machine.ChangeDefault(newDefault); err != nil {
+ return err
+ }
+ }
+
+ v.Rootful = opts.Rootful
+ return v.writeConfig()
+}
+
func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
if v.isRunning() {
return errors.Errorf("%q is already running", name)
@@ -716,6 +764,18 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error {
return errors.Wrap(err, "WSL bootstrap script failed")
}
+ if !v.Rootful {
+ fmt.Printf("\nThis machine is currently configured in rootless mode. If your containers\n")
+ fmt.Printf("require root permissions (e.g. ports < 1024), or if you run into compatibility\n")
+ fmt.Printf("issues with non-podman clients, you can switch using the following command: \n")
+
+ suffix := ""
+ if name != machine.DefaultMachineName {
+ suffix = " " + name
+ }
+ fmt.Printf("\n\tpodman machine set --rootful%s\n\n", suffix)
+ }
+
globalName, pipeName, err := launchWinProxy(v)
if err != nil {
fmt.Fprintln(os.Stderr, "API forwarding for Docker API clients is not available due to the following startup failures.")