Migrate to podman API rather than cli

Large change greatly expanding the linked footprint of this little
utility, but it is much faster and adds a couple of very nice features:

- set container sysctl settings to disable router advertisements as part
  of container definition. this means we no longer need to do this via
  `ip netns exec <container> sysctl -w ...` (followed by
  `ip netns exec ip -6 a flush...`). Major win.
- able to very quickly ascertain creation and run state of defined
  containers.
This commit is contained in:
Joel Elkins 2022-07-25 10:13:36 -05:00
parent c2ae18fed9
commit b758f04a50
8 changed files with 2507 additions and 255 deletions

View File

@ -31,8 +31,6 @@ func validNouns(*cobra.Command, []string, string) ([]string, cobra.ShellCompDire
for _, c := range config.Union([]string{}) {
validArgs = append(validArgs, c.Name)
}
for _, c := range config.Categories() {
validArgs = append(validArgs, c)
}
validArgs = append(validArgs, config.Categories()...)
return validArgs, cobra.ShellCompDirectiveNoFileComp
}

View File

@ -22,11 +22,13 @@ THE SOFTWARE.
package cmd
import (
"context"
"fmt"
"io"
"os"
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/containers/podman/v4/pkg/bindings"
"github.com/spf13/cobra"
)
@ -45,6 +47,7 @@ var (
output io.Writer
verbose bool
fake bool
socket string
)
// Execute adds all child commands to the root command and sets flags appropriately.
@ -58,7 +61,13 @@ func Execute() {
func init() {
cobra.OnInitialize(func() {
err := config.Init()
// connect to podman
conn, err := bindings.NewConnection(context.Background(), socket)
if err != nil {
fmt.Fprintln(os.Stderr, "Could not connect:", err)
os.Exit(1)
}
err = config.Init(conn)
if err != nil {
fmt.Fprintln(os.Stderr, "Warning: Could not initialize configuration:", err)
}
@ -73,4 +82,5 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&config.ConfigFile, "config", "c", config.CONFIG_FILE_DEFAULT, "pathname of config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show additional info from command execution")
rootCmd.PersistentFlags().BoolVarP(&fake, "no-action", "n", false, "do not actually execute commands")
rootCmd.PersistentFlags().StringVarP(&socket, "socket", "k", "unix:/run/podman/podman.sock", "socket address to which to connect")
}

99
go.mod
View File

@ -3,13 +3,112 @@ module gitea.elkins.co/Networking/ccl
go 1.18
require (
github.com/containers/podman/v4 v4.1.1
github.com/emirpasic/gods v1.18.1
github.com/pelletier/go-toml v1.9.5
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75
)
require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.1.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Microsoft/hcsshim v0.9.2 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/containerd/cgroups v1.0.3 // indirect
github.com/containerd/containerd v1.6.4 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/containers/buildah v1.26.1 // indirect
github.com/containers/common v0.48.0
github.com/containers/image/v5 v5.21.1 // indirect
github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a // indirect
github.com/containers/ocicrypt v1.1.4 // indirect
github.com/containers/psgo v1.7.2 // indirect
github.com/containers/storage v1.40.2 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/disiqueira/gotree/v3 v3.0.2 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.14+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.1-0.20210727194412-58542c764a11 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jinzhu/copier v0.3.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.2 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect
github.com/moby/sys/mountinfo v0.6.1 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
github.com/opencontainers/runc v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.0.3-0.20211214071223-8958f93039ab
github.com/opencontainers/runtime-tools v0.9.1-0.20220110225228-7e2d60f1e41f // indirect
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/ostreedev/ostree-go v0.0.0-20210805093236-719684c64e4f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/proglottis/gpgme v0.1.1 // indirect
github.com/prometheus/client_golang v1.11.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect
github.com/sylabs/sif/v2 v2.7.0 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vbauerster/mpb/v7 v7.4.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8 // indirect
google.golang.org/grpc v1.44.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/guregu/null.v4 v4.0.0
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

2080
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,8 @@ import (
"fmt"
"io"
"os/exec"
log "github.com/sirupsen/logrus"
)
type CommandType int
@ -32,7 +34,8 @@ type CommandType int
const (
CT_NOP CommandType = iota
CT_SH
CT_REF
CT_STRFUNC
CT_FUNC
CT_INDIRECT
CT_SET
CT_DEBUG
@ -45,7 +48,9 @@ func (ct CommandType) String() string {
return "NOP"
case CT_SH:
return "SHELL"
case CT_REF:
case CT_STRFUNC:
return "STRFUNC"
case CT_FUNC:
return "FUNC"
case CT_INDIRECT:
return "INDIRECT"
@ -70,6 +75,11 @@ type namedFunc struct {
Func func() string
}
type errFunc struct {
Name string
Func func() error
}
type conditional struct {
Name string
Condition func() bool
@ -81,12 +91,20 @@ func (f namedFunc) String() string {
return f.Name + "()"
}
func (f errFunc) String() string {
return f.Name + "()"
}
func NewShell(cmd string) Command {
return Command{CT_SH, cmd}
}
func NewFunc(name string, f func() string) Command {
return Command{CT_REF, namedFunc{name, f}}
return Command{CT_STRFUNC, namedFunc{name, f}}
}
func NewErrFunc(name string, f func() error) Command {
return Command{CT_FUNC, errFunc{name, f}}
}
func NewIndirect(c Command) Command {
@ -122,13 +140,13 @@ func (c Command) GetShell() (string, error) {
return s, fmt.Errorf("Type error. Requested = %s, Type = %s", CT_SH, c.Type)
}
func (c Command) CallRef() (string, error) {
func (c Command) CallStrFunc() (string, error) {
f, ok := c.Command.(namedFunc)
if ok {
s := f.Func()
return s, nil
}
return "", fmt.Errorf("Type error. Requested = %s, Type = %s", CT_REF, c.Type)
return "", fmt.Errorf("Type error. Requested = %s, Type = %s", CT_STRFUNC, c.Type)
}
func (c Command) Execute(output io.Writer, fake bool) error {
@ -146,13 +164,27 @@ func (c Command) Execute(output io.Writer, fake bool) error {
fmt.Fprint(output, string(out))
return err
}
case CT_REF:
case CT_STRFUNC:
fmt.Fprintln(output, c.Type, c.Command)
if !fake {
s, err := c.CallRef()
s, err := c.CallStrFunc()
fmt.Fprintln(output, s)
return err
}
case CT_FUNC:
ef := c.Command.(errFunc)
msg := log.WithFields(log.Fields{
"type": CT_FUNC.String(),
"func": ef.Name,
})
msg.Debugf("Calling")
fmt.Fprintln(output, c.Type, ef.Name)
if !fake {
if err := ef.Func(); err != nil {
return err
}
return nil
}
case CT_INDIRECT:
ct, ok := c.Command.(Command)
if !ok {

View File

@ -1,7 +1,7 @@
package config
import (
"fmt"
"context"
"os"
"gitea.elkins.co/Networking/ccl/internal/pkg/container"
@ -65,7 +65,7 @@ type parse struct {
Containers []container.Container
}
func Init() error {
func Init(conn context.Context) error {
f, err := os.ReadFile(ConfigFile)
if err != nil {
return err
@ -77,28 +77,7 @@ func Init() error {
}
containers, networks = &p.Containers, &p.Networks
for i := range p.Containers {
p.Containers[i].Init(networks)
p.Containers[i].Init(conn, networks)
}
return nil
}
func NetworkDefaults(name string) (net *network.Network) {
for _, n := range *networks {
if n.Name == name {
net = &n
}
}
return
}
func PrintCreate(ct *container.Container) {
for _, c := range ct.CreateCommands() {
fmt.Println(c)
}
}
func PrintStart(ct *container.Container) {
for _, c := range ct.StartCommands() {
fmt.Println(c)
}
}

View File

@ -22,14 +22,20 @@ THE SOFTWARE.
package container
import (
"context"
"fmt"
"os"
"net"
"os/exec"
"strconv"
"strings"
"gitea.elkins.co/Networking/ccl/internal/pkg/command"
"gitea.elkins.co/Networking/ccl/internal/pkg/network"
"github.com/containers/common/libnetwork/types"
"github.com/containers/podman/v4/libpod/define"
"github.com/containers/podman/v4/pkg/bindings/containers"
"github.com/containers/podman/v4/pkg/bindings/images"
"github.com/containers/podman/v4/pkg/specgen"
spec "github.com/opencontainers/runtime-spec/specs-go"
log "github.com/sirupsen/logrus"
)
type Container struct {
@ -37,138 +43,21 @@ type Container struct {
Name string
Image string
Hostname string
Command string
Command []string
Arguments string
Networks []network.Network
createCommands []command.Command
upCommands []command.Command
pid int
Env map[string]string
Mounts []spec.Mount
Restart string
Umask uint
User string
conn context.Context
cdata *define.InspectContainerData
}
func (c *Container) flushIPv6Commands() []command.Command {
cmds := []command.Command{}
flush6Routes := false
if len(c.Networks) > 0 && !*c.Networks[0].IPv6 {
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.default.accept_ra=0", c.Name)))
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.all.accept_ra=0", c.Name)))
flush6Routes = true
}
for i, n := range c.Networks {
// Note, n.IPv6 should never be nil, since we forcibly create the
// value in Init() if not specified in the config file
if !*n.IPv6 {
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.eth%d.accept_ra=0", c.Name, i)))
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s ip -6 address flush dev eth%d scope global", c.Name, i)))
flush6Routes = true
}
}
if flush6Routes {
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s ip -6 route flush proto ra", c.Name)))
}
return cmds
}
func (c *Container) CreateCommands() []command.Command {
if len(c.createCommands) == 0 {
c.initCommands()
}
if c.Arguments == "" {
fmt.Fprintf(os.Stderr, "Warning: Container %s does not have arguments defined! That's unusual", c.Name)
}
if c.Image == "" {
fmt.Fprintf(os.Stderr, "Error: Container %s does not have an image defined!", c.Name)
}
return c.createCommands
}
func (c *Container) RecreateCommands() []command.Command {
wasRunning := false
return []command.Command{
command.NewFunc("stash_run_state", func() string {
wasRunning = c.IsRunning()
runMsg := "not running. Will not start it after recreating."
if wasRunning {
runMsg = "running. Will restart after recreating."
}
return fmt.Sprintf("Container %s is %s.", c.Name, runMsg)
}),
command.NewSet(c.DestroyCommands()),
command.NewSet(c.CreateCommands()),
command.NewConditional("start_if_was_running",
func() bool { return wasRunning },
command.NewSet(c.StartCommands()),
command.NewNop(),
),
}
}
func (c *Container) DestroyCommands() []command.Command {
cmds := c.StopCommands()
cmds = append(cmds, command.NewShell("podman rm "+c.Name))
return cmds
}
func (c *Container) StartCommands() []command.Command {
if len(c.upCommands) == 0 {
c.initCommands()
}
return []command.Command{
command.NewConditional("start_unless_running",
c.IsRunning,
command.NewNop(),
command.NewSet(c.upCommands),
),
}
}
func (c *Container) RestartCommands() []command.Command {
return []command.Command{
command.NewSet(c.StopCommands()),
command.NewShell("sleep 1"),
command.NewSet(c.StartCommands()),
}
}
func (c *Container) IsRunning() bool {
pid, err := c.Pid()
if err != nil {
return false
}
return pid > 1
}
func (c *Container) UpdateCommands() []command.Command {
wasRunning := false
return []command.Command{
command.NewFunc("stash_run_state", func() string {
wasRunning = c.IsRunning()
runMsg := "not running"
if wasRunning {
runMsg = "running"
}
return "Container " + c.Name + " is " + runMsg
}),
command.NewShell("podman pull " + c.Image),
command.NewSet(c.RecreateCommands()),
command.NewConditional("restart_if_was_running",
func() bool { return wasRunning },
command.NewSet(c.StartCommands()),
command.NewNop(),
),
}
}
func (c *Container) StopCommands() []command.Command {
return []command.Command{
command.NewConditional("stop_if_running",
c.IsRunning,
command.NewShell("podman stop "+c.Name),
command.NewNop(),
),
}
}
func (c *Container) Init(nets *[]network.Network) {
func (c *Container) Init(conn context.Context, nets *[]network.Network) error {
// initialize user-provided definitions
for i := range c.Networks {
var n *network.Network
for j := range *nets {
@ -182,85 +71,260 @@ func (c *Container) Init(nets *[]network.Network) {
if len(c.Networks[i].DNS) == 0 {
c.Networks[i].DNS = n.DNS
}
if c.Networks[i].IPv6 == nil {
if n.IPv6 != nil {
if !c.Networks[i].IPv6.Valid {
if n.IPv6.Valid {
c.Networks[i].IPv6 = n.IPv6
} else {
yes := true
c.Networks[i].IPv6 = &yes
c.Networks[i].IPv6.SetValid(true)
}
}
}
for i := range c.Mounts {
if c.Mounts[i].Type == "" {
c.Mounts[i].Type = "bind"
}
}
c.conn = conn
if c.Umask == 0 {
c.Umask = 0o022
}
return c.populateCData()
}
func (c *Container) Pid() (pid int, err error) {
pid_s, err := exec.Command("podman", "inspect", "-f", "{{.State.Pid}}", c.Name).CombinedOutput()
if err != nil {
return
func (c *Container) CreateCommands() []command.Command {
if c.Image == "" {
log.WithField("container", c.Name).Error("Image not defined")
return []command.Command{command.NewNop()}
}
c.pid, err = strconv.Atoi(strings.TrimSuffix(string(pid_s), "\n"))
return c.pid, err
}
func (c *Container) initCommands() {
c.createCommands = []command.Command{
command.NewShell("podman create --name %s%s%s%s%s %s%s"),
sysctl := map[string]string{}
nets := map[string]types.PerNetworkOptions{}
dns := []net.IP{}
for i := range c.Networks {
if !c.Networks[i].IPv6.Bool {
sysctl["net.ipv6.conf."+c.Networks[i].Name+".accept_ra"] = "0"
}
hostname := ""
if c.Hostname != "" {
hostname = fmt.Sprintf(" --hostname %s", c.Hostname)
ips := []net.IP{}
if c.Networks[i].IPv4Address != nil {
ips = append(ips, c.Networks[i].IPv4Address)
}
net := ""
dns := ""
if len(c.Networks) > 0 {
net = " --net " + c.Networks[0].ToArgs()
if len(c.Networks[0].DNS) > 0 {
dns = " --dns " + strings.Join(c.Networks[0].DNS, ",")
if c.Networks[i].IPv6Address != nil {
ips = append(ips, c.Networks[i].IPv6Address)
}
nets[c.Networks[i].Name] = types.PerNetworkOptions{
StaticIPs: ips,
InterfaceName: c.Networks[i].Name,
}
args := ""
if c.Arguments != "" {
args = " " + c.Arguments
}
entry := ""
if c.Command != "" {
entry = " " + c.Command
}
t, _ := c.createCommands[0].GetShell()
c.createCommands[0] = command.NewShell(fmt.Sprintf(t, c.Name, hostname, net, dns, args, c.Image, entry))
if len(c.Networks) > 1 {
for i := 1; i < len(c.Networks); i++ {
n := c.Networks[i]
s := fmt.Sprintf("podman network connect %s %s", n.ToArgs(), c.Name)
c.createCommands = append(c.createCommands, command.NewShell(s))
}
dns = append(dns, c.Networks[i].DNS...)
}
c.upCommands = []command.Command{
command.NewShell("podman start " + c.Name),
command.NewFunc("assure_netns", func() string {
netns_b, err := exec.Command("podman", "inspect", "-f", "{{.NetworkSettings.SandboxKey}}", c.Name).CombinedOutput()
if err != nil {
return fmt.Sprintf("%s is not running", c.Name)
spec := specgen.SpecGenerator{
ContainerBasicConfig: specgen.ContainerBasicConfig{
Name: c.Name,
UtsNS: specgen.Namespace{NSMode: specgen.Private},
Hostname: c.Hostname,
RawImageName: c.Image,
RestartPolicy: c.Restart,
Sysctl: sysctl,
Env: c.Env,
Command: c.Command,
},
ContainerStorageConfig: specgen.ContainerStorageConfig{
Image: c.Image,
Mounts: c.Mounts,
},
ContainerNetworkConfig: specgen.ContainerNetworkConfig{
Networks: nets,
DNSServers: dns,
},
ContainerSecurityConfig: specgen.ContainerSecurityConfig{
User: c.User,
Umask: fmt.Sprintf("%#o", c.Umask),
},
}
netns := strings.TrimRight(string(netns_b), "\n")
err = exec.Command("rm", "-f", "/var/run/netns/"+c.Name).Run()
if err != nil {
return fmt.Sprintf("Error: %s", err)
if err := spec.Validate(); err != nil {
log.WithFields(log.Fields{
"container": c.Name,
"error": err,
}).Warn("Specgen does not validate")
}
err = exec.Command("ln", "-sf", netns, "/var/run/netns/"+c.Name).Run()
return []command.Command{
command.NewErrFunc("do_create", func() error {
_, err := containers.CreateWithSpec(c.conn, &spec, nil)
if err != nil {
fmt.Fprintln(os.Stderr, "Warning: could not establish network namespace", err)
return fmt.Sprintf("Error: %s", err)
return err
}
return fmt.Sprintf("Created /var/run/netns/%s", c.Name)
return c.populateCData()
}),
}
if len(c.Networks) > 0 && !*c.Networks[0].IPv6 {
c.upCommands = append(c.upCommands, command.NewShell("sleep 1"))
for _, k := range c.flushIPv6Commands() {
c.upCommands = append(c.upCommands, k)
}
func (c *Container) RecreateCommands() []command.Command {
wasRunning := false
return []command.Command{
command.NewFunc("stash_run_state", func() string {
wasRunning = c.IsRunning()
runMsg := "not running. Will not start it after recreating."
if wasRunning {
runMsg = "running. Will restart after recreating."
}
return fmt.Sprintf("Container %s is %s", c.Name, runMsg)
}),
command.NewSet(c.DestroyCommands()),
command.NewSet(c.CreateCommands()),
command.NewConditional("start_if_was_running",
func() bool { return wasRunning },
command.NewSet(c.StartCommands()),
command.NewNop(),
),
}
}
func (c *Container) DestroyCommands() []command.Command {
cmds := c.StopCommands()
cmds = append(cmds, command.NewErrFunc("remove_if_exists", func() error {
if c.cdata.ID == "" {
return nil
}
yes := true
_, err := containers.Remove(c.conn, c.cdata.ID, &containers.RemoveOptions{Force: &yes})
return err
}))
return cmds
}
func (c *Container) StartCommands() []command.Command {
return []command.Command{
command.NewErrFunc("start_container", func() error {
if c.cdata.State != nil && c.cdata.State.Running {
return nil
}
err := containers.Start(c.conn, c.cdata.ID, nil)
if err != nil {
return err
}
_, err = containers.Wait(c.conn, c.cdata.ID, &containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateRunning}})
if err != nil {
return err
}
err = c.populateCData()
if err != nil {
return err
}
err = c.assureNetNS()
if err != nil {
return err
}
return nil
}),
}
}
func (c *Container) RestartCommands() []command.Command {
return []command.Command{
command.NewSet(c.StopCommands()),
command.NewSet(c.StartCommands()),
}
}
func (c *Container) IsRunning() bool {
if c.cdata != nil && c.cdata.State != nil {
return c.cdata.State.Running
}
return false
}
func (c *Container) IsCreated() bool {
if c.cdata == nil || c.cdata.ID == "" {
return false
}
return true
}
func (c *Container) UpdateCommands() []command.Command {
wasRunning := false
return []command.Command{
command.NewErrFunc("do_update_and_stop", func() error {
_, err := images.Pull(c.conn, c.Image, &images.PullOptions{})
if err != nil {
return err
}
wasRunning = (c.cdata.State != nil) && c.cdata.State.Running
if wasRunning {
var timeout uint = 10
err := containers.Stop(c.conn, c.cdata.ID, &containers.StopOptions{Timeout: &timeout})
if err != nil {
return err
}
_, err = containers.Remove(c.conn, c.cdata.ID, nil)
if err != nil {
return err
}
c.cdata = nil
}
return nil
}),
command.NewSet(c.CreateCommands()),
command.NewConditional("restart_if_was_running",
func() bool { return wasRunning },
command.NewSet(c.StartCommands()),
command.NewNop(),
),
}
}
func (c *Container) StopCommands() []command.Command {
return []command.Command{
command.NewErrFunc("do_stop", func() error {
if c.IsRunning() {
var timeout uint = 10
err := containers.Stop(c.conn, c.cdata.ID, &containers.StopOptions{Timeout: &timeout})
if err != nil {
return err
}
_, err = containers.Wait(c.conn, c.cdata.ID, &containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateExited}})
if err != nil {
return err
}
return c.populateCData()
}
log.WithFields(log.Fields{
"container": c.Name,
"id": c.cdata.ID,
}).Debugf("Container stopped but wasn't running. Not a problem.")
return nil
}),
}
}
func (c *Container) populateCData() error {
// TODO: locking
var err error
size := false
c.cdata, err = containers.Inspect(c.conn, c.Name, &containers.InspectOptions{Size: &size})
if err != nil {
return err
}
return nil
}
func (c *Container) Pid() int {
if c.cdata != nil && c.cdata.State != nil {
return c.cdata.State.Pid
}
return 0
}
func (c *Container) assureNetNS() error {
if nil == c.cdata || nil == c.cdata.NetworkSettings {
return fmt.Errorf("Network namespace not available!")
}
netns := c.cdata.NetworkSettings.SandboxKey
if err := exec.Command("rm", "-f", "/var/run/netns/"+c.Name).Run(); err != nil {
return err
}
if err := exec.Command("ln", "-sf", netns, "/var/run/netns/"+c.Name).Run(); err != nil {
return err
}
return nil
}

View File

@ -21,26 +21,16 @@ THE SOFTWARE.
*/
package network
import "fmt"
import (
"net"
"gopkg.in/guregu/null.v4"
)
type Network struct {
Name string
DNS []string
IPv6 *bool `toml:"ipv6"`
IPv4Address string `toml:"ipv4_address"`
IPv6Address string `toml:"ipv6_address"`
}
func (n *Network) ToArgs() string {
net := "%s%s%s"
ipv4 := ""
if n.IPv4Address != "" {
ipv4 = fmt.Sprintf(" --ip %s", n.IPv4Address)
}
ipv6 := ""
if n.IPv6Address != "" {
ipv6 = fmt.Sprintf(" --ip6 %s", n.IPv6Address)
}
net = fmt.Sprintf(net, n.Name, ipv4, ipv6)
return net
DNS []net.IP
IPv6 null.Bool `toml:"ipv6"`
IPv4Address net.IP `toml:"ipv4_address"`
IPv6Address net.IP `toml:"ipv6_address"`
}