Skip to content

feat: Default to Podman Socket #1242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ ready-to-run images by injecting source code into a container image and letting

For a deep dive on S2I you can view [this presentation](https://www.youtube.com/watch?v=flI6zx9wH6M).

Want to try it right now? Download the [latest release](https://github.com/openshift/source-to-image/releases/latest) and run:
### Try It!

First, make sure your machine has either [Docker](https://www.docker.com) or [Podman](https://podman.io)
installed:

- s2i can work with Docker out of the box. However, the container image build will run in root
containers by default, which may pose a security risk.
- s2i can work with Podman by starting the Podman socket service. Passing `--user` to systemctl
ensures podman runs in rootless mode:
```sh
systemctl enable --user --now podman.socket
```

Next, download the [latest release](https://github.com/openshift/source-to-image/releases/latest) and run:

$ s2i build https://github.com/sclorg/django-ex centos/python-35-centos7 hello-python
$ docker run -p 8080:8080 hello-python
Expand Down
35 changes: 33 additions & 2 deletions pkg/docker/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ var (
log = utillog.StderrLog

// DefaultEntrypoint is the default entry point used when starting containers
DefaultEntrypoint = []string{"/usr/bin/env"}
DefaultEntrypoint = []string{"/usr/bin/env"}
DefaultPodmanSocket = filepath.Join("/run", "podman", "podman.sock")
)

// AuthConfigurations maps a registry name to an AuthConfig, as used for
Expand Down Expand Up @@ -434,7 +435,7 @@ func GetDefaultDockerConfig() *api.DockerConfig {
cfg := &api.DockerConfig{}

if cfg.Endpoint = os.Getenv("DOCKER_HOST"); cfg.Endpoint == "" {
cfg.Endpoint = client.DefaultDockerHost
cfg.Endpoint = GetDefaultContainerEngineHost()
}

certPath := os.Getenv("DOCKER_CERT_PATH")
Expand All @@ -457,6 +458,36 @@ func GetDefaultDockerConfig() *api.DockerConfig {
return cfg
}

// GetDefaultContainerEngineHost returns a default container engine socket address. It checks the
// following locations for a container engine socket:
//
// 1. Rootless podman socket: unix://$XDG_RUNTIME_DIR/podman/podman.sock
// 2. "Rootful" podman socket: unix:///run/podman/podman.sock
// 3. Docker socket: unix:///var/run/docker.sock
func GetDefaultContainerEngineHost() string {
podmanSockets := []string{}

// Podman socket provides a unix socket that is directly compatible with Docker
// There are two modes the socket can be provided:
//
// 1) "rootless" - runs as nonroot, with socket at $XDG_RUNTIME_DIR/podman/podman.sock
// 2) "rootful" - runs as root, with socket at /run/podman/podman.sock

if runtimeDir, ok := os.LookupEnv("XDG_RUNTIME_DIR"); ok {
podmanSockets = append(podmanSockets, filepath.Join(runtimeDir, "podman", "podman.sock"))
}
podmanSockets = append(podmanSockets, DefaultPodmanSocket)

for _, socket := range podmanSockets {
if _, err := os.Stat(socket); err == nil {
return fmt.Sprintf("unix://%s", socket)
}
}

// Fall back to default docker socket.
return client.DefaultDockerHost
}

// GetAssembleUser finds an assemble user on the given image.
// This functions receives the config to check if the AssembleUser was defined in command line
// If the cmd is blank, it tries to fetch the value from the Builder Image defined Label (assemble-user)
Expand Down
85 changes: 85 additions & 0 deletions pkg/docker/util_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package docker

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/docker/docker/client"

"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/api/constants"
"github.com/openshift/source-to-image/pkg/util/user"
Expand Down Expand Up @@ -233,14 +237,17 @@ func TestGetDefaultDockerConfig(t *testing.T) {
},
}
for _, tc := range tests {
oldXdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
oldHost := os.Getenv("DOCKER_HOST")
oldCertPath := os.Getenv("DOCKER_CERT_PATH")
oldTLSVerify := os.Getenv("DOCKER_TLS_VERIFY")
oldTLS := os.Getenv("DOCKER_TLS")
os.Setenv("XDG_RUNTIME_DIR", "")
os.Setenv("DOCKER_HOST", tc.envHost)
os.Setenv("DOCKER_CERT_PATH", tc.envCertPath)
os.Setenv("DOCKER_TLS_VERIFY", tc.envTLSVerify)
os.Setenv("DOCKER_TLS", tc.envTLS)
defer os.Setenv("XDG_RUNTIME_DIR", oldXdgRuntimeDir)
defer os.Setenv("DOCKER_HOST", oldHost)
defer os.Setenv("DOCKER_CERT_PATH", oldCertPath)
defer os.Setenv("DOCKER_TLS_VERIFY", oldTLSVerify)
Expand All @@ -262,6 +269,84 @@ func TestGetDefaultDockerConfig(t *testing.T) {
}
}

func TestGetDefaultContainerEngineHost(t *testing.T) {

tmpDir, err := os.MkdirTemp("", "s2i-container-engine-host-*")
if err != nil {
t.Fatalf("failed to create xdg temp dir: %v", err)
}
defer func() {
if rmErr := os.RemoveAll(tmpDir); rmErr != nil {
t.Errorf("failed to clean up xdg temp dir: %v", err)
}
}()

testCases := []struct {
name string
xdgRuntimeDir string
createPodmanSocket bool
expectedHost string
}{
{
name: "rootless podman - socket exists",
xdgRuntimeDir: tmpDir,
createPodmanSocket: true,
expectedHost: fmt.Sprintf("unix://%s", filepath.Join(tmpDir, "podman", "podman.sock")),
},
{
name: "rootless podman - socket does not exist",
xdgRuntimeDir: tmpDir,
createPodmanSocket: false,
expectedHost: client.DefaultDockerHost,
},
{
name: "docker default",
expectedHost: client.DefaultDockerHost,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
oldXdgDir := os.Getenv("XDG_RUNTIME_DIR")
os.Setenv("XDG_RUNTIME_DIR", tc.xdgRuntimeDir)
defer os.Setenv("XDG_RUNTIME_DIR", oldXdgDir)

socketCreated := false
socketPath := filepath.Join(tc.xdgRuntimeDir, "podman", "podman.sock")
if tc.createPodmanSocket {

if _, err := os.Stat(socketPath); err != nil {
if err := os.MkdirAll(filepath.Dir(socketPath), 0750); err != nil {
t.Fatalf("failed to create socket directory: %v", err)
}
file, err := os.Create(socketPath)
if err != nil {
t.Fatalf("failed to create default podman socket: %v", err)
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
t.Errorf("failed to close file: %v", closeErr)
}
}()
socketCreated = true
}
}

engineHost := GetDefaultContainerEngineHost()
if tc.expectedHost != engineHost {
t.Errorf("expected container host %s; got %s", tc.expectedHost, engineHost)
}

if socketCreated {
if err := os.RemoveAll(filepath.Dir(socketPath)); err != nil {
t.Errorf("failed to clean up podman socket: %v", err)
}
}

})
}
}

func TestGetAssembleUser(t *testing.T) {
testCases := []struct {
name string
Expand Down