Skip to content
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

podman http api client #51

Merged
merged 34 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eeb5c43
podman http api client initial commit
towe75 Jul 20, 2020
8c7b915
Merge branch 'master' into f-http-api
towe75 Jul 31, 2020
951c53a
Implement ContainerStart http api call
towe75 Jul 31, 2020
e9d30e1
Implement ContailerDelete http api call
towe75 Aug 2, 2020
d0591f2
Switch podman repo to opensuse
towe75 Aug 2, 2020
de69df0
Implement ContainerInspect and SystemInfo http api calls
towe75 Aug 4, 2020
ab0bcba
Linter
towe75 Aug 5, 2020
5d363e6
License headers
towe75 Aug 6, 2020
4ac65cf
linter, fmt
towe75 Aug 28, 2020
6f05360
Remove separate go setup, rely on base image
towe75 Aug 28, 2020
b2c8e24
Makefile improvements
towe75 Aug 29, 2020
4ca9b04
implemented api v2 create container
towe75 Aug 31, 2020
819cd82
Merged SignalTask support and linter changes
towe75 Sep 1, 2020
84bb809
Improved tmpfs unittest
towe75 Sep 1, 2020
f06c899
Implement ContainerKill / SignalTask http api call
towe75 Sep 6, 2020
c9bb044
Fix SignalTask unittest
towe75 Sep 6, 2020
34807fd
Implement ContainerStats http api call
towe75 Oct 31, 2020
220228c
Code cleanup
towe75 Nov 1, 2020
745fa54
Removed varlink from tests
towe75 Nov 1, 2020
dac3c0d
Remove varlink code
towe75 Nov 1, 2020
c394552
Fix test, lint
towe75 Nov 1, 2020
d49b67f
Pull image before create container
towe75 Nov 2, 2020
4a5c6ee
Fix ContainerInstpect, adopt some loglevels
towe75 Nov 2, 2020
b2cb7a3
Fixed various bugs
towe75 Nov 3, 2020
d1eddc5
Comments
towe75 Nov 5, 2020
3241eb9
Merge branch 'master' into f-http-api
towe75 Nov 5, 2020
205f71e
Code review #51
towe75 Nov 6, 2020
48050ff
Code review #51
towe75 Nov 6, 2020
1e6ba5a
Change license headers to MPL-2
towe75 Nov 6, 2020
072b63b
Code review #51
towe75 Nov 6, 2020
7f6d2f2
Removed license headers completely
towe75 Nov 9, 2020
c952215
Code review #51
towe75 Nov 9, 2020
4e201ba
Added a ClientConfig struct with default values
towe75 Nov 15, 2020
f52f9ee
Review #51
towe75 Nov 16, 2020
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
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/user@1000.service/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/user@1000.service/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