mirror of
https://github.com/rancher/os.git
synced 2025-08-29 03:31:25 +00:00
Add simple API to do most container functions
This commit is contained in:
parent
ab783a2183
commit
e38e790374
290
docker/container.go
Normal file
290
docker/container.go
Normal file
@ -0,0 +1,290 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/runconfig"
|
||||
dockerClient "github.com/fsouza/go-dockerclient"
|
||||
"github.com/rancherio/os/config"
|
||||
"github.com/rancherio/os/util"
|
||||
)
|
||||
|
||||
const (
|
||||
LABEL = "label"
|
||||
HASH = "io.rancher.os.hash"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Err error
|
||||
Name string
|
||||
remove bool
|
||||
detach bool
|
||||
Config *runconfig.Config
|
||||
HostConfig *runconfig.HostConfig
|
||||
cfg *config.Config
|
||||
container *dockerClient.Container
|
||||
containerCfg *config.ContainerConfig
|
||||
}
|
||||
|
||||
func getHash(containerCfg *config.ContainerConfig) (string, error) {
|
||||
hash := sha1.New()
|
||||
w := util.NewErrorWriter(hash)
|
||||
|
||||
w.Write([]byte(containerCfg.Id))
|
||||
w.Write([]byte(strings.Join(containerCfg.Cmd, ":")))
|
||||
|
||||
if w.Err != nil {
|
||||
return "", w.Err
|
||||
}
|
||||
|
||||
return hex.EncodeToString(hash.Sum([]byte{})), nil
|
||||
}
|
||||
|
||||
func StartAndWait(cfg *config.Config, containerCfg *config.ContainerConfig) error {
|
||||
container := NewContainer(cfg, containerCfg).start(true)
|
||||
return container.Err
|
||||
}
|
||||
|
||||
func NewContainer(cfg *config.Config, containerCfg *config.ContainerConfig) *Container {
|
||||
return &Container{
|
||||
cfg: cfg,
|
||||
containerCfg: containerCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) returnErr(err error) *Container {
|
||||
c.Err = err
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) Lookup() *Container {
|
||||
c.Parse()
|
||||
|
||||
if c.Err != nil || (c.container != nil && c.container.HostConfig != nil) {
|
||||
return c
|
||||
}
|
||||
|
||||
hash, err := getHash(c.containerCfg)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
client, err := NewClient(c.cfg)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
containers, err := client.ListContainers(dockerClient.ListContainersOptions{
|
||||
All: true,
|
||||
Filters: map[string][]string{
|
||||
LABEL: []string{fmt.Sprintf("%s=%s", HASH, hash)},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return c
|
||||
}
|
||||
|
||||
c.container, c.Err = client.InspectContainer(containers[0].ID)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) Exists() bool {
|
||||
c.Lookup()
|
||||
return c.container != nil
|
||||
}
|
||||
|
||||
func (c *Container) Reset() *Container {
|
||||
c.Config = nil
|
||||
c.HostConfig = nil
|
||||
c.container = nil
|
||||
c.Err = nil
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) Parse() *Container {
|
||||
if c.Config != nil || c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
flags := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
|
||||
flRemove := flags.Bool([]string{"#rm", "-rm"}, false, "")
|
||||
flDetach := flags.Bool([]string{"d", "-detach"}, false, "")
|
||||
flName := flags.String([]string{"#name", "-name"}, "", "")
|
||||
|
||||
c.Config, c.HostConfig, _, c.Err = runconfig.Parse(flags, c.containerCfg.Cmd)
|
||||
|
||||
c.Name = *flName
|
||||
c.detach = *flDetach
|
||||
c.remove = *flRemove
|
||||
|
||||
if len(c.containerCfg.Id) == 0 {
|
||||
c.containerCfg.Id = c.Name
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) Start() *Container {
|
||||
return c.start(false)
|
||||
}
|
||||
|
||||
func (c *Container) Stage() *Container {
|
||||
c.Parse()
|
||||
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
client, err := NewClient(c.cfg)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return c
|
||||
}
|
||||
|
||||
_, err = client.InspectImage(c.Config.Image)
|
||||
if err == dockerClient.ErrNoSuchImage {
|
||||
c.Err = client.PullImage(dockerClient.PullImageOptions{
|
||||
Repository: c.Config.Image,
|
||||
OutputStream: os.Stdout,
|
||||
}, dockerClient.AuthConfiguration{})
|
||||
} else if err != nil {
|
||||
c.Err = err
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Container) Delete() *Container {
|
||||
c.Parse()
|
||||
c.Stage()
|
||||
c.Lookup()
|
||||
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
if !c.Exists() {
|
||||
return c
|
||||
}
|
||||
|
||||
client, err := NewClient(c.cfg)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
err = client.RemoveContainer(dockerClient.RemoveContainerOptions{
|
||||
ID: c.container.ID,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func renameOld(client *dockerClient.Client, opts *dockerClient.CreateContainerOptions) error {
|
||||
if len(opts.Name) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
existing, err := client.InspectContainer(opts.Name)
|
||||
if _, ok := err.(dockerClient.NoSuchContainer); ok {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if label, ok := existing.Config.Labels[HASH]; ok {
|
||||
return client.RenameContainer(existing.ID, fmt.Sprintf("%s-%s", existing.Name, label))
|
||||
} else {
|
||||
//TODO: do something with containers with no hash
|
||||
return errors.New("Existing container doesn't have a hash")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) start(wait bool) *Container {
|
||||
c.Lookup()
|
||||
c.Stage()
|
||||
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
client, err := NewClient(c.cfg)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
var opts dockerClient.CreateContainerOptions
|
||||
container := c.container
|
||||
created := false
|
||||
|
||||
if !c.Exists() {
|
||||
c.Err = json.Unmarshal(bytes, &opts)
|
||||
if c.Err != nil {
|
||||
return c
|
||||
}
|
||||
|
||||
if opts.Config.Labels == nil {
|
||||
opts.Config.Labels = make(map[string]string)
|
||||
}
|
||||
|
||||
hash, err := getHash(c.containerCfg)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
opts.Config.Labels[HASH] = hash
|
||||
|
||||
err = renameOld(client, &opts)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
container, err = client.CreateContainer(opts)
|
||||
created = true
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
c.container = container
|
||||
|
||||
hostConfig := container.HostConfig
|
||||
if created {
|
||||
hostConfig = opts.HostConfig
|
||||
}
|
||||
|
||||
err = client.StartContainer(container.ID, hostConfig)
|
||||
if err != nil {
|
||||
return c.returnErr(err)
|
||||
}
|
||||
|
||||
if !c.detach && wait {
|
||||
_, c.Err = client.WaitContainer(container.ID)
|
||||
return c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
134
docker/container_test.go
Normal file
134
docker/container_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rancherio/os/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
hash, err := getHash(&config.ContainerConfig{
|
||||
Id: "id",
|
||||
Cmd: []string{"1", "2", "3"},
|
||||
})
|
||||
assert.NoError(err, "")
|
||||
|
||||
hash2, err := getHash(&config.ContainerConfig{
|
||||
Id: "id2",
|
||||
Cmd: []string{"1", "2", "3"},
|
||||
})
|
||||
assert.NoError(err, "")
|
||||
|
||||
hash3, err := getHash(&config.ContainerConfig{
|
||||
Id: "id3",
|
||||
Cmd: []string{"1", "2", "3", "4"},
|
||||
})
|
||||
assert.NoError(err, "")
|
||||
|
||||
assert.Equal("44096e94ed438ccda24e459412147441a376ea1c", hash, "")
|
||||
assert.NotEqual(hash, hash2, "")
|
||||
assert.NotEqual(hash2, hash3, "")
|
||||
assert.NotEqual(hash, hash3, "")
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
cfg := &config.ContainerConfig{
|
||||
Cmd: []string{
|
||||
"--name", "c1",
|
||||
"-d",
|
||||
"--rm",
|
||||
"--privileged",
|
||||
"test/image",
|
||||
"arg1",
|
||||
"arg2",
|
||||
},
|
||||
}
|
||||
|
||||
c := NewContainer(nil, cfg).Parse()
|
||||
|
||||
assert.NoError(c.Err, "")
|
||||
assert.Equal(cfg.Id, "c1", "Id doesn't match")
|
||||
assert.Equal(c.Name, "c1", "Name doesn't match")
|
||||
assert.True(c.remove, "Remove doesn't match")
|
||||
assert.True(c.detach, "Detach doesn't match")
|
||||
assert.Equal(len(c.Config.Cmd), 2, "Args doesn't match")
|
||||
assert.Equal(c.Config.Cmd[0], "arg1", "Arg1 doesn't match")
|
||||
assert.Equal(c.Config.Cmd[1], "arg2", "Arg2 doesn't match")
|
||||
assert.True(c.HostConfig.Privileged, "Privileged doesn't match")
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
c := NewContainer(nil, &config.ContainerConfig{
|
||||
Cmd: []string{"--pid=host", "--privileged", "--rm", "busybox", "echo", "hi"},
|
||||
}).Parse().Start().Lookup()
|
||||
|
||||
assert.NoError(c.Err, "")
|
||||
|
||||
assert.True(c.HostConfig.Privileged, "")
|
||||
assert.True(c.container.HostConfig.Privileged, "")
|
||||
assert.Equal("host", c.container.HostConfig.PidMode, "")
|
||||
|
||||
c.Delete()
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
cfg := &config.ContainerConfig{
|
||||
Cmd: []string{"--rm", "busybox", "echo", "hi"},
|
||||
}
|
||||
c := NewContainer(nil, cfg).Parse().Start()
|
||||
|
||||
cfg2 := &config.ContainerConfig{
|
||||
Cmd: []string{"--rm", "busybox", "echo", "hi2"},
|
||||
}
|
||||
c2 := NewContainer(nil, cfg2).Parse().Start()
|
||||
|
||||
assert.NoError(c.Err, "")
|
||||
assert.NoError(c2.Err, "")
|
||||
|
||||
c1Lookup := NewContainer(nil, cfg).Lookup()
|
||||
c2Lookup := NewContainer(nil, cfg2).Lookup()
|
||||
|
||||
assert.NoError(c1Lookup.Err, "")
|
||||
assert.NoError(c2Lookup.Err, "")
|
||||
|
||||
assert.Equal(c.container.ID, c1Lookup.container.ID, "")
|
||||
assert.Equal(c2.container.ID, c2Lookup.container.ID, "")
|
||||
|
||||
c.Delete()
|
||||
c2.Delete()
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
assert := require.New(t)
|
||||
|
||||
c := NewContainer(nil, &config.ContainerConfig{
|
||||
Cmd: []string{"--rm", "busybox", "echo", "hi"},
|
||||
}).Parse()
|
||||
|
||||
assert.False(c.Exists())
|
||||
assert.NoError(c.Err, "")
|
||||
|
||||
c.Start()
|
||||
assert.NoError(c.Err, "")
|
||||
c.Reset()
|
||||
assert.NoError(c.Err, "")
|
||||
|
||||
assert.True(c.Exists())
|
||||
assert.NoError(c.Err, "")
|
||||
|
||||
c.Delete()
|
||||
assert.NoError(c.Err, "")
|
||||
|
||||
c.Reset()
|
||||
assert.False(c.Exists())
|
||||
assert.NoError(c.Err, "")
|
||||
}
|
Loading…
Reference in New Issue
Block a user