This repository has been archived by the owner on Oct 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replaced custom docker compose reader with docker/cli.
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
1 parent
6c8419b
commit 1bacaaf
Showing
12 changed files
with
563 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.