Skip to main content

Programmatic remote access to Podman via the varlink protocol

· 8 min read

podman logo

Programmatic remote access to Podman via the varlink protocol

By Harald Hoyer GitHub

This guide shows how to access Podman remotely via the varlink interface with CLI tools and programmatically with python, go and rust.

This should work on Linux, MacOS and Windows 10.

The compatibility matrix shows which feature is supported on which OS in which language.

Note: replace <podman-machine> in this guide with the IP or hostname of your Podman machine

Prerequisites

Windows ssh

If you are on a windows client machine, install the OpenSSH Client built by Microsoft in a cmd.exe in admin mode:

> dism /online /Add-Capability /CapabilityName:OpenSSH.Client~~~~0.0.1.0

Close cmd.exe window.

Note: Works also with other ssh clients, e.g. ssh from Git Bash.

Generate ssh keys

If you don't want to type your password all the time, or not use an ssh agent, set an empty password.

$ ssh-keygen -f ~/.ssh/podmanuser

Set up Podman on the Fedora/RHEL machine

$ sudo yum install podman libvarlink-util
$ sudo groupadd podman

Copy /lib/tmpfiles.d/podman.conf to /etc/tmpfiles.d/podman.conf.

$ sudo cp /lib/tmpfiles.d/podman.conf /etc/tmpfiles.d/podman.conf

Edit /etc/tmpfiles.d/podman.conf:

d /run/podman 0750 root podman

Copy /lib/systemd/system/io.podman.socket to /etc/systemd/system/io.podman.socket.

$ sudo cp /lib/systemd/system/io.podman.socket /etc/systemd/system/io.podman.socket

Edit section [Socket] of /etc/systemd/system/io.podman.socket:

[Socket]
ListenStream=/run/podman/io.podman
SocketMode=0660
SocketGroup=podman

Then activate the changes:

$ sudo systemctl daemon-reload
$ sudo systemd-tmpfiles --create
$ sudo systemctl enable --now io.podman.socket

The directory and socket now belongs to the podman group

$ sudo ls -al /run/podman
drwxr-x---. 2 root podman 60 14. Jan 14:50 .
drwxr-xr-x. 51 root root 1420 14. Jan 14:36 ..
srw-rw----. 1 root podman 0 14. Jan 14:50 io.podman

Note: Wouldn't it be nice, if there was a Podman group owning the socket already? ;-)

Now we are adding a user podmanuser and set a password:

$ sudo useradd podmanuser -G podman
$ sudo passwd podmanuser

From your client machine do

$ ssh-copy-id -f ~/.ssh/podmanuser podmanuser@<podman-machine>

ssh config

Edit .ssh/config

Host <podman-machine>
RequestTTY no
IdentityFile ~/.ssh/podmanuser
User podmanuser
VisualHostKey no
RemoteCommand /usr/bin/varlink bridge --connect unix:/run/podman/io.podman
GSSAPIAuthentication no
ForwardX11 no

Optional Lock Down

Log into <podman-machine>

$ ssh podmanuser@<podman-machine>

Now we lock down podmanuser to only be used with the varlink bridge from your client machine:

Edit .ssh/authorized-keys so that the line begins with:

command="/usr/bin/varlink bridge --connect unix:/run/podman/io.podman",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa […]

Log out of <podman-machine>

Python

Install Python

https://www.python.org/downloads/

$ pip install --user "varlink>=30.0.2"
$ python -m varlink.cli --help
usage: cli.py [-h] [-r RESOLVER] [-A ACTIVATE] [-b BRIDGE]
{info,help,bridge,call} ...

Interfacing Podman with the python cli module

$ python -m varlink.cli --bridge "ssh <podman-machine>" info
info
.1:1234
Vendor: Atomic
Product: podman
Version: 0.10.1
URL: https://github.com/containers/podman
Interfaces:
org.varlink.service
io.podman

$ python -m varlink.cli --bridge "ssh <podman-machine>" call io.podman.Ping {}
{
"ping": {
"message": "OK"
}
}

Python Client Example

podmanclient.py:

import varlink

with varlink.Client.new_with_bridge(["ssh", "<podman-machine>"]) as client:
with client.open("io.podman") as podman:
print(podman.Ping())
print(podman.GetInfo())
print(podman.GetVersion())

info = podman.GetInfo()
print("Uptime:", info["info"]["host"]["uptime"])
print("Os:", info["info"]["host"]["os"])

try:
podman.MountContainer("container-id")
except varlink.error.VarlinkError as e:
print(e.error(), e.parameters())
print(e.as_dict())

To find out more about the Podman varlink interface read the io.podman.varlink file or the rendered API.md.

Or you can inspect, what methods your Podman version on <podman-machine> provides:

$ python -m varlink.cli --bridge "ssh <podman-machine>" help io.podman

Go

Installation

$ go get -u github.com/varlink/go/varlink
$ go install github.com/varlink/go/cmd/varlink
$ go install github.com/varlink/go/cmd/varlink-go-interface-generator

The varlink CLI command in $GOPATH/bin should output:

$ varlink --bridge "ssh <podman-machine>" info
Vendor: Atomic
Product: podman
Version: 0.10.1
URL: https://github.com/containers/podman
Interfaces:
org.varlink.service
io.podman
$ varlink --bridge "ssh <podman-machine>" call io.podman.Ping
{
"ping": {
"message": "OK"
}
}

$ varlink --bridge "ssh <podman-machine>" call io.podman.MountContainer "{\"name\": \"container-id\"}"
Error: Call failed with error: io.podman.ErrorOccurred
{
"reason": "no container with name or ID container-id found: no such container"
}

To find out more about the Podman varlink interface read the io.podman.varlink file or the rendered API.md.

Or you can inspect, what methods your Podman version on <podman-machine> provides:

$ varlink --bridge "ssh <podman-machine>" help io.podman

Go Client Example

Either clone this repository or:

Create a new go project. Create a sub directory iopodman in the project.

Create the io.podman.varlink either from the podman github sources or dynamically with:

$ varlink --bridge "ssh <podman-machine>" help io.podman > iopodman/io.podman.varlink

Create iopodman/generate.go:

package iopodman

//go:generate $GOPATH/bin/varlink-go-interface-generator io.podman.varlink

Run go generate:

$ go generate ./...

Create your main.go:

package main

import (
"flag"
"fmt"
"github.com/haraldh/podmangoexampleclient/iopodman"
"github.com/varlink/go/varlink"
"io"
"os"
)

func printError(methodname string, err error) {
fmt.Fprintf(os.Stderr, "Error calling %s: ", methodname)
switch e := err.(type) {
case *iopodman.ImageNotFound:
//error ImageNotFound (name: string)
fmt.Fprintf(os.Stderr, "'%v' name='%s'\n", e, e.Name)

case *iopodman.ContainerNotFound:
//error ContainerNotFound (name: string)
fmt.Fprintf(os.Stderr, "'%v' name='%s'\n", e, e.Name)

case *iopodman.NoContainerRunning:
//error NoContainerRunning ()
fmt.Fprintf(os.Stderr, "'%v'\n", e)

case *iopodman.PodNotFound:
//error PodNotFound (name: string)
fmt.Fprintf(os.Stderr, "'%v' name='%s'\n", e, e.Name)

case *iopodman.PodContainerError:
//error PodContainerError (podname: string, errors: []PodContainerErrorData)
fmt.Fprintf(os.Stderr, "'%v' podname='%s' errors='%v'\n", e, e.Podname, e.Errors)

case *iopodman.NoContainersInPod:
//error NoContainersInPod (name: string)
fmt.Fprintf(os.Stderr, "'%v' name='%s'\n", e, e.Name)

case *iopodman.ErrorOccurred:
//error ErrorOccurred (reason: string)
fmt.Fprintf(os.Stderr, "'%v' reason='%s'\n", e, e.Reason)

case *iopodman.RuntimeError:
//error RuntimeError (reason: string)
fmt.Fprintf(os.Stderr, "'%v' reason='%s'\n", e, e.Reason)

case *varlink.InvalidParameter:
fmt.Fprintf(os.Stderr, "'%v' parameter='%s'\n", e, e.Parameter)

case *varlink.MethodNotFound:
fmt.Fprintf(os.Stderr, "'%v' method='%s'\n", e, e.Method)

case *varlink.MethodNotImplemented:
fmt.Fprintf(os.Stderr, "'%v' method='%s'\n", e, e.Method)

case *varlink.InterfaceNotFound:
fmt.Fprintf(os.Stderr, "'%v' interface='%s'\n", e, e.Interface)

case *varlink.Error:
fmt.Fprintf(os.Stderr, "'%v' parameters='%v'\n", e, e.Parameters)

default:
if err == io.EOF {
fmt.Fprintf(os.Stderr, "Connection closed\n", )
} else if err == io.ErrUnexpectedEOF {
fmt.Fprintf(os.Stderr, "Connection aborted\n", )
} else {
fmt.Fprintf(os.Stderr, "%T - '%v'\n", err, err)
}
}
}

func main() {
var c *varlink.Connection
var err error

c, err = varlink.NewBridge("ssh <podman-machine>")
if err != nil {
fmt.Fprintf(os.Stderr, "Error connecting: %T - '%v'\n", err, err)
os.Exit(1)
}

// Be nice and cleanup
defer c.Close()

info, err := iopodman.GetInfo().Call(c)

if err != nil {
printError("GetInfo()", err)
os.Exit(1)
}

fmt.Printf("Info: %+v\n\n", info)

fmt.Printf("Podman Version: %+v\n\n", info.Podman.Podman_version)

containers, err := iopodman.ListContainers().Call(c)

if err != nil {
printError("ListContainers()", err)
os.Exit(1)
}

for container := range containers {
print(container)
}

mount, err := iopodman.MountContainer().Call(c, "foo")
if err != nil {
printError("MountContainer()", err)
} else {
print(mount)
}
}

Rust

Install the rust toolchain

Windows

First install the C++ part of https://visualstudio.microsoft.com/downloads/

All

https://rustup.rs/

For non-Linux systems:

$ cargo install varlink-cli

Note: Ensure that $HOME/.cargo/bin is in your PATH or copy $HOME/.cargo/bin/varlink in one of your path directories

For Linux systems:

You can also use varlink util from libvarlink or install libvarlink-util on Fedora/RHEL machines.

The varlink CLI command in ~/.cargo/bin should output:

$ varlink --bridge "ssh <podman-machine>" info
Vendor: Atomic
Product: podman
Version: 0.10.1
URL: https://github.com/containers/podman
Interfaces:
org.varlink.service
io.podman
$ varlink --bridge "ssh <podman-machine>" call io.podman.Ping
{
"ping": {
"message": "OK"
}
}

$ varlink --bridge "ssh <podman-machine>" call io.podman.MountContainer "{\"name\": \"container-id\"}"
Error: Call failed with error: io.podman.ErrorOccurred
{
"reason": "no container with name or ID container-id found: no such container"
}

To find out more about the Podman varlink interface read the io.podman.varlink file or the rendered API.md.

Or you can inspect, what methods your Podman version on <podman-machine> provides:

$ varlink --bridge "ssh <podman-machine>" help io.podman

Rust Client Example

Either clone this repository or:

$ cargo new --bin podmanrs
$ cd podmanrs

Download the varlink interface from the running Podman varlink service:

$ varlink --bridge "ssh <podman-machine>" help io.podman > src/io.podman.varlink

create build.rs:

extern crate varlink_generator;

fn main() {
varlink_generator::cargo_build_tosource("src/io.podman.varlink", true);
}

create Cargo.toml:

[package]
name = "podmanrs"
version = "0.1.0"
authors = ["Harald Hoyer <harald@redhat.com>"]
build = "build.rs"
edition = "2018"

[dependencies]
varlink = "7"
serde = "1"
serde_derive = "1"
serde_json = "1"
chainerror = "0.4"
[build-dependencies]
varlink_generator = "7"

create src/main.rs:

mod io_podman;

use crate::io_podman::*;
use varlink::Connection;
use std::result::Result;
use std::error::Error;

fn main() -> Result<(), Box<Error>> {
let connection = Connection::with_bridge(
"ssh <podman-machine>",
)?;
let mut podman = VarlinkClient::new(connection.clone());
let reply = podman.ping().call()?;
println!("Ping() replied with '{}'", reply.ping.message);
let reply = podman.get_info().call()?;
println!("Hostname: {}", reply.info.host.hostname);
println!("Info: {:#?}", reply.info);
Ok(())
}

Now run it:

$ cargo run