Skip to content
This repository has been archived by the owner on Oct 26, 2023. It is now read-only.

Commit

Permalink
Replaced custom docker compose reader with docker/cli.
Browse files Browse the repository at this point in the history
Supports Docker Compose 3 format only. Older formats will be supported when
docker/cli#573 is merged and containers/image updated their docker/docker dependency
  • Loading branch information
mgoltzsche committed Feb 25, 2018
1 parent 6c8419b commit 169b01c
Show file tree
Hide file tree
Showing 12 changed files with 564 additions and 34 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ binary: dependencies

test: dependencies
# Run tests. TODO: more tests
GOPATH="${GOPATH}" go test -tags "${BUILDTAGS}" "${PKGNAME}/model"
#GOPATH="${GOPATH}" go test -tags "${BUILDTAGS}" "${PKGNAME}/model"
GOPATH="${GOPATH}" go test -tags "${BUILDTAGS}" "${PKGNAME}/model/compose"

format:
# Format the go code
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Besides cntnr is a platform to try out new runc features.
- Image and bundle file system creation using [umoci](https://github.com/openSUSE/umoci)
- Various image formats and transports supported by [containers/image](https://github.com/containers/image)
- Optional container networking using [CNI](https://github.com/containernetworking/cni) (as OCI runtime hook)
- Partial [docker compose](https://docs.docker.com/compose/compose-file/) file format support
- Partial [Docker Compose 3](https://docs.docker.com/compose/compose-file/) support using [docker/cli](https://github.com/docker/cli/)
- Simple CLI partially compatible with [docker](https://www.docker.com/)'s
- Easy installation: single statically linked binary (plus optional binaries: CNI plugins, proot) and convention over configuration

Expand Down Expand Up @@ -145,6 +145,7 @@ cntnr uses [runc/libcontainer](https://github.com/opencontainers/runc/blob/v1.0.
- [runrootless](https://github.com/AkihiroSuda/runrootless)
- [singularity](http://singularity.lbl.gov/)
- [orca-build](https://github.com/cyphar/orca-build)
- [rkt-compose](https://github.com/mgoltzsche/rkt-compose)


## Roadmap
Expand Down
3 changes: 2 additions & 1 deletion cmd/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package cmd

import (
"github.com/mgoltzsche/cntnr/model"
"github.com/mgoltzsche/cntnr/model/compose"
"github.com/spf13/cobra"
)

Expand All @@ -42,7 +43,7 @@ func runComposeRun(cmd *cobra.Command, args []string) error {
return usageError("No compose file argument provided")
}

project, err := model.LoadProject(args[0], loggers.Warn)
project, err := compose.Load(args[0], ".", compose.GetEnv(), loggers.Warn)
if err != nil {
return err
}
Expand Down
5 changes: 3 additions & 2 deletions cmd/serviceflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ func (c *bundleFlags) InitRunFlags(f *pflag.FlagSet) {

func (c *bundleFlags) curr() *model.Service {
if c.app == nil {
c.app = model.NewService("")
s := model.NewService("")
c.app = &s
}
return c.app
}
Expand Down Expand Up @@ -300,7 +301,7 @@ type cVolumeMount bundleFlags

func (c *cVolumeMount) Set(s string) (err error) {
v := model.VolumeMount{}
if err = model.ParseVolumeMount(s, &v); err != nil {
if err = model.ParseBindMount(s, &v); err != nil {
return
}
v.Source, err = filepath.Abs(v.Source)
Expand Down
242 changes: 242 additions & 0 deletions model/compose/dctransform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package compose

import (
"io/ioutil"
"os"
"strings"

"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/types"
"github.com/mgoltzsche/cntnr/log"
"github.com/mgoltzsche/cntnr/model"
exterrors "github.com/mgoltzsche/cntnr/pkg/errors"
"github.com/mgoltzsche/cntnr/pkg/sliceutils"
"github.com/pkg/errors"
)

//
// Currently only Docker Compose 3 schema is supported.
// Let's hope soon the older schema versions are supported as well
// when this is merged: https://github.com/docker/cli/pull/573
// and containers/image updated their github.com/docker/docker dependency
//

// TODO: use project
func Load(file, cwd string, env map[string]string, warn log.Logger) (r *model.CompoundServices, err error) {
defer exterrors.Wrapd(&err, "load docker compose file")
b, err := ioutil.ReadFile(file)
if err != nil {
return
}
dcyml, err := loader.ParseYAML(b)
if err != nil {
return
}
cfg, err := loader.Load(types.ConfigDetails{
WorkingDir: cwd,
ConfigFiles: []types.ConfigFile{types.ConfigFile{file, dcyml}},
Environment: env,
})
if err != nil {
return
}
return transform(cfg, cwd, warn)
}

func GetEnv() map[string]string {
r := map[string]string{}
for _, entry := range os.Environ() {
s := strings.SplitN(entry, "=", 2)
if len(s) == 2 {
r[s[0]] = s[1]
} else {
r[s[0]] = ""
}
}
return r
}

func transform(cfg *types.Config, cwd string, warn log.Logger) (r *model.CompoundServices, err error) {
services, err := toServices(cfg.Services)
if err != nil {
return
}
r = &model.CompoundServices{
Dir: cwd,
Volumes: toVolumes(cfg.Volumes, warn),
Services: services,
// TODO: map networks, secrets
}
return
}

func toVolumes(vols map[string]types.VolumeConfig, warn log.Logger) map[string]model.Volume {
r := map[string]model.Volume{}
for name, vol := range vols {
v := model.Volume{}
if vol.External.External {
v.External = vol.Name
if vol.External.Name != "" {
v.External = vol.External.Name
}
} else {
warn.Printf("adding unsupported volume %v as temporary volume", vol)
}
r[name] = v
}
return r
}

func toServices(services []types.ServiceConfig) (r map[string]model.Service, err error) {
r = map[string]model.Service{}
for _, service := range services {
if r[service.Name], err = toService(service); err != nil {
return
}
}
return
}

func toService(s types.ServiceConfig) (r model.Service, err error) {
r = model.NewService(s.Name)
r.Build = toBuild(s.Build)
r.CapAdd = s.CapAdd
r.CapDrop = s.CapDrop
// s.CgroupParent
r.Command = []string(s.Command)
// TODO:
// DependsOn
// CredentialSpec
// Deploy
// Devices
r.Dns = []string(s.DNS)
r.DnsSearch = []string(s.DNSSearch)
r.Domainname = s.DomainName
r.Entrypoint = []string(s.Entrypoint)
r.Environment = toStringMap(s.Environment)
// EnvFile
r.Expose = []string(s.Expose)
// ExternalLinks
if r.ExtraHosts, err = toExtraHosts(s.ExtraHosts); err != nil {
return
}
r.Hostname = s.ContainerName
// Healthcheck
r.Image = s.Image
// Ipc
// Labels
// Links
// Logging
// MacAddress
// NetworkMode
// Pid
if r.Ports, err = toPorts(s.Ports); err != nil {
return
}
// Privileged
r.ReadOnly = s.ReadOnly
// Restart
// Secrets
// SecurityOpt
r.StdinOpen = s.StdinOpen
r.StopGracePeriod = s.StopGracePeriod
r.StopSignal = s.StopSignal
// Tmpfs
r.Tty = s.Tty
// Ulimits
r.User = toUser(s.User)
r.Volumes = toVolumeMounts(s.Volumes)
r.Cwd = s.WorkingDir
// Isolation
return
}

func toBuild(s types.BuildConfig) (r *model.ImageBuild) {
if s.Context != "" || s.Dockerfile != "" {
r = &model.ImageBuild{
Context: s.Context,
Dockerfile: s.Dockerfile,
Args: toStringMap(s.Args),
}
}
return
}

func toStringMap(m types.MappingWithEquals) map[string]string {
r := map[string]string{}
for k, v := range (map[string]*string)(m) {
if v == nil {
r[k] = ""
} else {
r[k] = *v
}
}
return r
}

func toExtraHosts(hl types.HostsList) ([]model.ExtraHost, error) {
l := []string(hl)
r := make([]model.ExtraHost, 0, len(l))
for _, h := range hl {
he := strings.SplitN(h, ":", 2)
if len(he) != 2 {
return nil, errors.Errorf("invalid extra_hosts entry: expected format host:ip but was %q", h)
}
r = append(r, model.ExtraHost{
Name: he[0],
Ip: he[1],
})
}
return r, nil
}

func toPorts(ports []types.ServicePortConfig) (r []model.PortBinding, err error) {
r = make([]model.PortBinding, 0, len(ports))
for _, p := range ports {
if p.Target > 65535 {
return nil, errors.Errorf("invalid target port %d exceeded range", p.Target)
}
if p.Published > 65535 {
return nil, errors.Errorf("invalid published port %d exceeded range", p.Published)
}
r = append(r, model.PortBinding{
Target: uint16(p.Target),
Published: uint16(p.Published),
Protocol: p.Protocol,
})
// TODO: checkout p.Mode
}
return
}

func toUser(s string) (u *model.User) {
ug := strings.SplitN(s, ":", 2)
if len(ug) == 2 {
u = &model.User{ug[0], ug[1]}
} else {
u = &model.User{ug[0], ug[0]}
}
return
}

func toVolumeMounts(vols []types.ServiceVolumeConfig) []model.VolumeMount {
r := []model.VolumeMount{}
for _, vol := range vols {
var opts []string
if vol.Bind != nil && vol.Bind.Propagation != "" {
opts = strings.Split(vol.Bind.Propagation, ":")
}
if vol.ReadOnly {
sliceutils.AddToSet(&opts, "ro")
}
// TODO: resolve entry of type 'volume'
r = append(r, model.VolumeMount{
Type: vol.Type, // 'volume', 'bind' or 'tmpfs'
Source: vol.Source,
Target: vol.Target,
Options: opts,
// TODO: Tmpfs, Consistency
})
}
return r
}
27 changes: 27 additions & 0 deletions model/compose/dctransform_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package compose

import (
"fmt"
"io/ioutil"
"testing"

"github.com/mgoltzsche/cntnr/log"
"github.com/mgoltzsche/cntnr/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoad(t *testing.T) {
dcFile := "../../vendor/github.com/docker/cli/cli/compose/loader/full-example.yml"
b, err := ioutil.ReadFile("full-example.json")
require.NoError(t, err)
expected, err := model.FromJSON(b)
require.NoError(t, err)
env := map[string]string{}
env["HOME"] = "/home/user"
actual, err := Load(dcFile, "../../vendor/github.com/docker/cli/cli/compose/loader", env, log.NewNopLogger())
require.NoError(t, err)
fmt.Println(actual.JSON())
assert.Equal(t, expected.Services, actual.Services)
assert.Equal(t, expected.Volumes, actual.Volumes)
}
Loading

0 comments on commit 169b01c

Please sign in to comment.