Skip to content

Commit

Permalink
contd: read environment variable from .stratup.toml if it exists and …
Browse files Browse the repository at this point in the history
…ensure /etc/resolv.conf (#318)

* contd: read environment variable from .stratup.toml if it exists

* sort list before comparing

* contd: parse startup.toml and update container data

fixes #319

we extract 3 things from statup.toml:
- entrypoint
- working dir
- enviroment variable
  • Loading branch information
zaibon authored Oct 17, 2019
1 parent 04a6942 commit 58b9940
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 53 deletions.
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)
}

0 comments on commit 58b9940

Please sign in to comment.