Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/threefoldtech/zos
go 1.13

require (
github.com/BurntSushi/toml v0.3.1
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412
github.com/alexflint/go-filemutex v0.0.0-20171028004239-d358565f3c3f
Expand Down
2 changes: 2 additions & 0 deletions pkg/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type Container struct {
RootFS string
// Env env variables to container in format {'KEY=VALUE', 'KEY2=VALUE2'}
Env []string
// WorkingDir of the entrypoint command
WorkingDir string
// Network network info for container
Network NetworkInfo
// Mounts extra mounts for container
Expand Down
65 changes: 58 additions & 7 deletions pkg/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package container
import (
"context"

"github.com/BurntSushi/toml"
"github.com/pkg/errors"

"fmt"
Expand Down Expand Up @@ -62,12 +63,7 @@ func New(root string, containerd string) pkg.ContainerModule {
}

// Run creates and starts a container
// THIS IS A WIP Create action and it's not fully implemented atm
func (c *containerModule) Run(ns string, data pkg.Container) (id pkg.ContainerID, err error) {
log.Info().
Str("namesapce", ns).
Str("data", fmt.Sprintf("%+v", data)).
Msgf("create new container")
// create a new client connected to the default socket path for containerd
client, err := containerd.New(c.containerd)
if err != nil {
Expand All @@ -85,8 +81,8 @@ func (c *containerModule) Run(ns string, data pkg.Container) (id pkg.ContainerID
return id, ErrEmptyRootFS
}

if err := setResolvConf(data.RootFS); err != nil {
return id, errors.Wrap(err, "failed to set resolv.conf")
if err := applyStartup(&data, filepath.Join(data.RootFS, ".startup.toml")); err != nil {
errors.Wrap(err, "error updating environment variable from startup file")
}

if data.Interactive {
Expand All @@ -113,8 +109,14 @@ func (c *containerModule) Run(ns string, data pkg.Container) (id pkg.ContainerID
oci.WithRootFSPath(data.RootFS),
oci.WithProcessArgs(args...),
oci.WithEnv(data.Env),
oci.WithHostResolvconf,
removeRunMount(),
}

if data.WorkingDir != "" {
opts = append(opts, oci.WithProcessCwd(data.WorkingDir))
}

if data.Interactive {
opts = append(
opts,
Expand Down Expand Up @@ -152,6 +154,11 @@ func (c *containerModule) Run(ns string, data pkg.Container) (id pkg.ContainerID
)
}

log.Info().
Str("namespace", ns).
Str("data", fmt.Sprintf("%+v", data)).
Msgf("create new container")

container, err := client.NewContainer(
ctx,
data.Name,
Expand All @@ -166,6 +173,7 @@ func (c *containerModule) Run(ns string, data pkg.Container) (id pkg.ContainerID
return id, err
}
log.Info().Msgf("args %+v", spec.Process.Args)
log.Info().Msgf("env %+v", spec.Process.Env)
log.Info().Msgf("root %+v", spec.Root)
for _, linxNS := range spec.Linux.Namespaces {
log.Info().Msgf("namespace %+v", linxNS.Type)
Expand Down Expand Up @@ -298,3 +306,46 @@ func (c *containerModule) Delete(ns string, id pkg.ContainerID) error {

return container.Delete(ctx)
}

func (c *containerModule) ensureNamespace(ctx context.Context, client *containerd.Client, namespace string) error {
service := client.NamespaceService()
namespaces, err := service.List(ctx)
if err != nil {
return err
}

for _, ns := range namespaces {
if ns == namespace {
return nil
}
}

return service.Create(ctx, namespace, nil)
}

func applyStartup(data *pkg.Container, path string) error {
f, err := os.Open(path)
if err == nil {
defer f.Close()
log.Info().Msg("startup file found")

startup := startup{}
if _, err := toml.DecodeReader(f, &startup); err != nil {
return err
}

entry, ok := startup.Entries["entry"]
if !ok {
return nil
}

data.Env = mergeEnvs(entry.Envs(), data.Env)
if data.Entrypoint == "" && entry.Entrypoint() != "" {
data.Entrypoint = entry.Entrypoint()
}
if data.WorkingDir == "" && entry.WorkingDir() != "" {
data.WorkingDir = entry.WorkingDir()
}
}
return nil
}
49 changes: 3 additions & 46 deletions pkg/container/spec_opts.go → pkg/container/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package container

import (
"context"
"os"

"path"
"path/filepath"

"github.com/containerd/containerd"
"github.com/opencontainers/runtime-spec/specs-go"

"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
"github.com/opencontainers/runtime-spec/specs-go"
)

// withNetworkNamespace set the named network namespace to use for the container
Expand Down Expand Up @@ -57,22 +56,6 @@ func withAddedCapabilities(caps []string) oci.SpecOpts {
}
}

func (c *containerModule) ensureNamespace(ctx context.Context, client *containerd.Client, namespace string) error {
service := client.NamespaceService()
namespaces, err := service.List(ctx)
if err != nil {
return err
}

for _, ns := range namespaces {
if ns == namespace {
return nil
}
}

return service.Create(ctx, namespace, nil)
}

func removeRunMount() oci.SpecOpts {
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error {
for i, mount := range s.Mounts {
Expand All @@ -84,29 +67,3 @@ func removeRunMount() oci.SpecOpts {
return nil
}
}

func setResolvConf(root string) error {
const tmp = "nameserver 1.1.1.1\nnameserver 1.0.0.1\n2606:4700:4700::1111\nnameserver 2606:4700:4700::1001\n"

path := filepath.Join(root, "etc/resolv.conf")
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 644)
if err != nil && !os.IsNotExist(err) {
return err
}

defer f.Close()

if os.IsNotExist(err) {
_, err = f.WriteString(tmp)
return err
}

info, err := f.Stat()
if err != nil {
return err
}
if info.Size() == 0 {
_, err = f.WriteString(tmp)
}
return err
}
63 changes: 63 additions & 0 deletions pkg/container/startup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package container

import (
"fmt"
"strings"
)

type startup struct {
Entries map[string]entry `toml:"startup"`
}

type entry struct {
Name string
Args args
}

type args struct {
Name string
Dir string
Env map[string]string
}

func (e entry) Entrypoint() string {
if e.Name == "core.system" ||
e.Name == "core.base" && e.Args.Name != "" {
return e.Args.Name
}
return ""
}

func (e entry) WorkingDir() string {
return e.Args.Dir
}

func (e entry) Envs() []string {
envs := make([]string, 0, len(e.Args.Env))
for k, v := range e.Args.Env {
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
}
return envs
}

// mergeEnvs merge a into b
// all the key from a will endup in b
// if a key is present in both, key from a are kept
func mergeEnvs(a, b []string) []string {
m := make(map[string]string, len(a)+len(b))

for _, s := range b {
ss := strings.SplitN(s, "=", 2)
m[ss[0]] = ss[1]
}
for _, s := range a {
ss := strings.SplitN(s, "=", 2)
m[ss[0]] = ss[1]
}

result := make([]string, 0, len(m))
for k, v := range m {
result = append(result, fmt.Sprintf("%s=%s", k, v))
}
return result
}
96 changes: 96 additions & 0 deletions pkg/container/startup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package container

import (
"sort"
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/BurntSushi/toml"
"github.com/stretchr/testify/require"
)

var _startup = `[startup]

[startup.entry]
name = "core.system"

[startup.entry.args]
name = "/start"
dir = "/data"

[startup.entry.args.env]
DIFFICULTY = "easy"
LEVEL = "world"
SERVER_PORT = "25565"
`

func TestParseStartup(t *testing.T) {
r := strings.NewReader(_startup)
e := startup{}
_, err := toml.DecodeReader(r, &e)
require.NoError(t, err)

entry, ok := e.Entries["entry"]
require.True(t, ok)
assert.Equal(t, "core.system", entry.Name)
assert.Equal(t, "/start", entry.Args.Name)
assert.Equal(t, "/data", entry.Args.Dir)
assert.Equal(t, map[string]string{
"DIFFICULTY": "easy",
"LEVEL": "world",
"SERVER_PORT": "25565",
}, entry.Args.Env)
}

func TestStartupEntrypoint(t *testing.T) {
r := strings.NewReader(_startup)
e := startup{}
_, err := toml.DecodeReader(r, &e)
require.NoError(t, err)

entry, ok := e.Entries["entry"]
require.True(t, ok)
assert.Equal(t, entry.Entrypoint(), "/start")
}

func TestStartupEnvs(t *testing.T) {
r := strings.NewReader(_startup)
e := startup{}
_, err := toml.DecodeReader(r, &e)
require.NoError(t, err)

entry, ok := e.Entries["entry"]
require.True(t, ok)
actual := entry.Envs()
sort.Strings(actual)
expected := []string{
"DIFFICULTY=easy",
"LEVEL=world",
"SERVER_PORT=25565",
}
assert.Equal(t, expected, actual)
}

func TestStartupWorkingDir(t *testing.T) {
r := strings.NewReader(_startup)
e := startup{}
_, err := toml.DecodeReader(r, &e)
require.NoError(t, err)

entry, ok := e.Entries["entry"]
require.True(t, ok)
assert.Equal(t, entry.WorkingDir(), "/data")
}

func TestMergeEnvs(t *testing.T) {
actual := mergeEnvs(
[]string{"FOO=BAR", "HELLO=WORLD"},
[]string{"HELLO=HELLO"},
)

expected := []string{"FOO=BAR", "HELLO=WORLD"}
sort.Strings(actual)
assert.Equal(t, expected, actual)
}