1
Fork 0

preliminary unit tests

main
Motiejus Jakštys 2021-05-24 00:11:58 +03:00
parent 2ec4c0629b
commit eb9f4d5400
4 changed files with 148 additions and 23 deletions

View File

@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
@ -6,3 +6,13 @@ go_library(
importpath = "github.com/motiejus/code/undocker/lxcconfig",
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["lxcconfig_test.go"],
embed = [":go_default_library"],
deps = [
"@com_github_stretchr_testify//assert:go_default_library",
"@com_github_stretchr_testify//require:go_default_library",
],
)

View File

@ -7,32 +7,89 @@ import (
"fmt"
"io"
"strings"
"text/template"
)
const _manifestJSON = "manifest.json"
// dockerManifest is manifest.json
type dockerManifest []struct {
Config string `json:"Config"`
}
// dockerConfig returns interesting configs for the container. user/group are
// skipped, since Docker allows specifying them by name, which would require
// peeking into the container image's /etc/passwd to resolve the names to ints.
type dockerConfig struct {
Architecture string `json:"architecture"`
Config struct {
Entrypoint []string `json:"Entrypoint"`
Cmd []string `json:"Cmd"`
Env []string `json:"Env"`
WorkingDir string `json:"WorkingDir"`
} `json:"config"`
}
const (
_json = ".json"
_manifestJSON = "manifest.json"
)
var (
_lxcTemplate = template.Must(
template.New("lxcconfig").Parse("" +
"lxc.include = LXC_TEMPLATE_CONFIG/common.conf\n" +
"lxc.architecture = {{ .Architecture }}\n" +
"lxc.execute.cmd = '{{ .Cmd }}'\n" +
"{{ if .Cwd }}lxc.init.cwd = {{ .Cwd }}\n{{ end }}" +
"{{ range .Env }}lxc.environment = {{ . }}\n{{ end }}"))
errBadManifest = errors.New("bad or missing manifest.json")
)
type (
// lxcConfig is passed to _lxcTemplate
lxcConfig struct {
Architecture string
Cmd string
Cwd string
Env []string
}
// dockerManifest is manifest.json
dockerManifest []struct {
Config string `json:"Config"`
}
// dockerConfig returns interesting configs for the container. user/group are
// skipped, since Docker allows specifying them by name, which would require
// peeking into the container image's /etc/passwd to resolve the names to ints.
dockerConfig struct {
Architecture string `json:"architecture"`
Config dockerConfigConfig `json:"config"`
}
dockerConfigConfig struct {
Entrypoint []string `json:"Entrypoint"`
Cmd []string `json:"Cmd"`
WorkingDir string `json:"WorkingDir"`
Env []string `json:"Env"`
}
)
// LXCConfig accepts a Docker container image and returns lxc configuration.
func LXCConfig(in io.ReadSeeker, wr io.Writer) error {
dockerCfg, err := getDockerConfig(in)
if err != nil {
return err
}
lxcCfg := docker2lxc(dockerCfg)
return lxcCfg.WriteTo(wr)
}
func docker2lxc(d dockerConfig) lxcConfig {
// cmd/entrypoint logic is copied from lxc-oci template
ep := strings.Join(d.Config.Entrypoint, " ")
cmd := strings.Join(d.Config.Cmd, " ")
if len(ep) == 0 {
ep = cmd
if len(ep) == 0 {
ep = "/bin/sh"
}
} else if len(cmd) != 0 {
ep = ep + " " + cmd
}
return lxcConfig{
Architecture: d.Architecture,
Cmd: ep,
Env: d.Config.Env,
Cwd: d.Config.WorkingDir,
}
}
func (l lxcConfig) WriteTo(wr io.Writer) error {
return _lxcTemplate.Execute(wr, l)
}
func getDockerConfig(in io.ReadSeeker) (dockerConfig, error) {
tr := tar.NewReader(in)
// get offsets to all json files in the archive
@ -45,7 +102,7 @@ func getDockerConfig(in io.ReadSeeker) (dockerConfig, error) {
if hdr.Typeflag != tar.TypeReg {
continue
}
if !strings.HasSuffix(".json", hdr.Name) {
if !strings.HasSuffix(_json, hdr.Name) {
continue
}
here, err := in.Seek(0, io.SeekCurrent)

View File

@ -0,0 +1,58 @@
package lxcconfig
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLXCConfig(t *testing.T) {
tests := []struct {
name string
docker dockerConfig
want string
}{
{
name: "just architecture",
docker: dockerConfig{
Architecture: "amd64",
},
want: `lxc.include = LXC_TEMPLATE_CONFIG/common.conf
lxc.architecture = amd64
lxc.execute.cmd = '/bin/sh'
`,
},
{
name: "all fields",
docker: dockerConfig{
Architecture: "amd64",
Config: dockerConfigConfig{
Entrypoint: []string{"/entrypoint.sh"},
Cmd: []string{"/bin/sh", "-c", "echo foo"},
WorkingDir: "/x",
Env: []string{
`LONGNAME="Foo Bar"`,
"SHELL=/bin/tcsh",
},
},
},
want: `lxc.include = LXC_TEMPLATE_CONFIG/common.conf
lxc.architecture = amd64
lxc.execute.cmd = '/entrypoint.sh /bin/sh -c echo foo'
lxc.init.cwd = /x
lxc.environment = LONGNAME="Foo Bar"
lxc.environment = SHELL=/bin/tcsh
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
require.NoError(t, docker2lxc(tt.docker).WriteTo(&buf))
assert.Equal(t, tt.want, string(buf.Bytes()))
})
}
}

View File

@ -36,9 +36,9 @@ type dockerManifestJSON []struct {
// I) layer name
// II) offset (0 being the first file in the layer)
// 4. go through
func RootFS(in io.ReadSeeker, out io.Writer) (err error) {
func RootFS(in io.ReadSeeker, wr io.Writer) (err error) {
tr := tar.NewReader(in)
tw := tar.NewWriter(out)
tw := tar.NewWriter(wr)
defer func() { err = multierr.Append(err, tw.Close()) }()
// layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset