Skip to content

Commit

Permalink
podman http api client (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
towe75 authored Nov 25, 2020
1 parent 52db084 commit b580f2d
Show file tree
Hide file tree
Showing 33 changed files with 2,443 additions and 13,613 deletions.
40 changes: 20 additions & 20 deletions .github/machinesetup.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/bash -e

# add podman repository
echo "deb http://ppa.launchpad.net/projectatomic/ppa/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/podman.list
apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 018BA5AD9DF57A4448F0E6CF8BECF1637AD8C79D
# add podman 2.x repository
echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04/ /" | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04/Release.key | apt-key add -

# Ignore apt-get update errors to avoid failing due to misbehaving repo;
# true errors would fail in the apt-get install phase
Expand All @@ -25,37 +25,37 @@ podman info
echo "====== Podman version:"
podman version

# enable varlink socket (not included in ubuntu package)
cat > /etc/systemd/system/io.podman.service << EOF
# enable http socket (not included in ubuntu package)
cat > /etc/systemd/system/podman.service << EOF
[Unit]
Description=Podman Remote API Service
Requires=io.podman.socket
After=io.podman.socket
Documentation=man:podman-varlink(1)
Description=Podman API Service
Requires=podman.socket
After=podman.socket
Documentation=man:podman-system-service(1)
StartLimitIntervalSec=0
[Service]
Type=simple
ExecStart=/usr/bin/podman varlink unix:%t/podman/io.podman --timeout=60000
TimeoutStopSec=30
KillMode=process
ExecStart=/usr/bin/podman system service
[Install]
WantedBy=multi-user.target
Also=io.podman.socket
Also=podman.socket
EOF

cat > /etc/systemd/system/io.podman.socket << EOF
cat > /etc/systemd/system/podman.socket << EOF
[Unit]
Description=Podman Remote API Socket
Documentation=man:podman-varlink(1)
Description=Podman API Socket
Documentation=man:podman-system-service(1)
[Socket]
ListenStream=%t/podman/io.podman
SocketMode=0600
ListenStream=%t/podman/podman.sock
SocketMode=0660
[Install]
WantedBy=sockets.targett
WantedBy=sockets.target
EOF

systemctl daemon-reload
systemctl start io.podman
# enable http api
systemctl start podman
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ linters-settings:
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
maligned:
suggest-new: true


linters:
Expand Down
32 changes: 13 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ cd nomad-driver-podman
- Linux host with `podman` installed
- For rootless containers you need a system supporting cgroup V2 and a few other things, follow [this tutorial](https://github.com/containers/libpod/blob/master/docs/tutorials/rootless_tutorial.md)

You need a varlink enabled podman binary and a system socket activation unit,
see https://podman.io/blogs/2019/01/16/podman-varlink.html.
You need a 2.x podman binary and a system socket activation unit,
see https://www.redhat.com/sysadmin/podmans-new-rest-api

nomad agent, nomad-driver-podman and podman will reside on the same host, so you
do not have to worry about the ssh aspects of podman varlink.
do not have to worry about the ssh aspects of the podman api.

Ensure that nomad can find the plugin, see [plugin_dir](https://www.nomadproject.io/docs/configuration/index.html#plugin_dir)

Expand Down Expand Up @@ -334,22 +334,16 @@ GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1 systemd.uni

`sudo update-grub`

ensure that podman varlink is running
```
$ systemctl --user status io.podman
● io.podman.service - Podman Remote API Service
Loaded: loaded (/usr/lib/systemd/user/io.podman.service; disabled; vendor preset: enabled)
Active: active (running) since Wed 2020-07-01 16:01:41 EDT; 7s ago
TriggeredBy: ● io.podman.socket
Docs: man:podman-varlink(1)
Main PID: 25091 (podman)
Tasks: 29 (limit: 18808)
Memory: 17.5M
CPU: 184ms
CGroup: /user.slice/user-1000.slice/[email protected]/io.podman.service
├─25091 /usr/bin/podman varlink unix:/run/user/1000/podman/io.podman --timeout=60000 --cgroup-manager=systemd
├─25121 /usr/bin/podman varlink unix:/run/user/1000/podman/io.podman --timeout=60000 --cgroup-manager=systemd
└─25125 /usr/bin/podman
ensure that podman socket is running
```
$ systemctl --user status podman.socket
* podman.socket - Podman API Socket
Loaded: loaded (/usr/lib/systemd/user/podman.socket; disabled; vendor preset: disabled)
Active: active (listening) since Sat 2020-10-31 19:21:29 CET; 22h ago
Triggers: * podman.service
Docs: man:podman-system-service(1)
Listen: /run/user/1000/podman/podman.sock (Stream)
CGroup: /user.slice/user-1000.slice/[email protected]/podman.socket
```

ensure that you have a recent version of [crun](https://github.com/containers/crun/)
Expand Down
95 changes: 95 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package api

import (
"context"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"time"

"github.com/hashicorp/go-hclog"
)

type API struct {
baseUrl string
httpClient *http.Client
logger hclog.Logger
}

type ClientConfig struct {
SocketPath string
HttpTimeout time.Duration
}

func DefaultClientConfig() ClientConfig {
cfg := ClientConfig{
HttpTimeout: 60 * time.Second,
}
uid := os.Getuid()
// are we root?
if uid == 0 {
cfg.SocketPath = "unix:/run/podman/podman.sock"
} else {
// not? then let's try the default per-user socket location
cfg.SocketPath = fmt.Sprintf("unix:/run/user/%d/podman/podman.sock", uid)
}
return cfg
}

func NewClient(logger hclog.Logger, config ClientConfig) *API {
ac := &API{
logger: logger,
}

baseUrl := config.SocketPath
ac.logger.Debug("http baseurl", "url", baseUrl)
ac.httpClient = &http.Client{
Timeout: config.HttpTimeout,
}
if strings.HasPrefix(baseUrl, "unix:") {
ac.baseUrl = "http://u"
path := strings.TrimPrefix(baseUrl, "unix:")
ac.httpClient.Transport = &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", path)
},
}
} else {
ac.baseUrl = baseUrl
}

return ac
}

func (c *API) Do(req *http.Request) (*http.Response, error) {
res, err := c.httpClient.Do(req)
return res, err
}

func (c *API) Get(ctx context.Context, path string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "GET", c.baseUrl+path, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}

func (c *API) Post(ctx context.Context, path string, body io.Reader) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "POST", c.baseUrl+path, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
return c.Do(req)
}

func (c *API) Delete(ctx context.Context, path string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, "DELETE", c.baseUrl+path, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
77 changes: 77 additions & 0 deletions api/container_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package api

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

// ContainerCreate creates a new container
func (c *API) ContainerCreate(ctx context.Context, create SpecGenerator) (ContainerCreateResponse, error) {

response := ContainerCreateResponse{}

jsonString, err := json.Marshal(create)
if err != nil {
return response, err
}

res, err := c.Post(ctx, "/v1.0.0/libpod/containers/create", bytes.NewBuffer(jsonString))
if err != nil {
return response, err
}

defer res.Body.Close()

if res.StatusCode != http.StatusCreated {
body, _ := ioutil.ReadAll(res.Body)
return response, fmt.Errorf("unknown error, status code: %d: %s", res.StatusCode, body)
}

body, err := ioutil.ReadAll(res.Body)
if err != nil {
return response, err
}
err = json.Unmarshal(body, &response)
if err != nil {
return response, err
}

return response, err
}

type ContainerCreateRequest struct {
// Name is the name the container will be given.
// If no name is provided, one will be randomly generated.
// Optional.
Name string `json:"name,omitempty"`

// Command is the container's command.
// If not given and Image is specified, this will be populated by the
// image's configuration.
// Optional.
Command []string `json:"command,omitempty"`

// Entrypoint is the container's entrypoint.
// If not given and Image is specified, this will be populated by the
// image's configuration.
// Optional.
Entrypoint []string `json:"entrypoint,omitempty"`

// WorkDir is the container's working directory.
// If unset, the default, /, will be used.
// Optional.
WorkDir string `json:"work_dir,omitempty"`
// Env is a set of environment variables that will be set in the
// container.
// Optional.
Env map[string]string `json:"env,omitempty"`
}

type ContainerCreateResponse struct {
Id string
Warnings []string
}
24 changes: 24 additions & 0 deletions api/container_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package api

import (
"context"
"fmt"
"net/http"
)

// ContainerDelete deletes a container.
// It takes the name or ID of a container.
func (c *API) ContainerDelete(ctx context.Context, name string, force bool, deleteVolumes bool) error {

res, err := c.Delete(ctx, fmt.Sprintf("/v1.0.0/libpod/containers/%s?force=%t&v=%t", name, force, deleteVolumes))
if err != nil {
return err
}

defer res.Body.Close()

if res.StatusCode == http.StatusNoContent {
return nil
}
return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
}
36 changes: 36 additions & 0 deletions api/container_inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package api

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

// ContainerInspect data takes a name or ID of a container returns the inspection data
func (c *API) ContainerInspect(ctx context.Context, name string) (InspectContainerData, error) {

var inspectData InspectContainerData

res, err := c.Get(ctx, fmt.Sprintf("/v1.0.0/libpod/containers/%s/json", name))
if err != nil {
return inspectData, err
}

defer res.Body.Close()

if res.StatusCode != http.StatusOK {
return inspectData, fmt.Errorf("unknown error, status code: %d", res.StatusCode)
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return inspectData, err
}
err = json.Unmarshal(body, &inspectData)
if err != nil {
return inspectData, err
}

return inspectData, nil
}
23 changes: 23 additions & 0 deletions api/container_kill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package api

import (
"context"
"fmt"
"net/http"
)

// ContainerKill sends a signal to a container
func (c *API) ContainerKill(ctx context.Context, name string, signal string) error {

res, err := c.Post(ctx, fmt.Sprintf("/v1.0.0/libpod/containers/%s/kill?signal=%s", name, signal), nil)
if err != nil {
return err
}

defer res.Body.Close()

if res.StatusCode == http.StatusNoContent {
return nil
}
return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
}
Loading

0 comments on commit b580f2d

Please sign in to comment.