├── .gitignore ├── dcc_suite_test.go ├── v1_test.go ├── README.md ├── functions.go ├── Gopkg.toml ├── convertable.go ├── main.go ├── Gopkg.lock ├── v1tov23.go ├── v1.go ├── v2.3.go ├── v3.2.go ├── v3.3.go ├── v1tov32_test.go └── v1tov32.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.yml 2 | vendor/ 3 | -------------------------------------------------------------------------------- /dcc_suite_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestDcc(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Dcc Suite") 13 | } 14 | -------------------------------------------------------------------------------- /v1_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("V1", func() { 9 | Describe("asCpus", func() { 10 | 11 | tests := map[CpuQuota]string{ 12 | 250000: "2.5", 13 | 11000: "0.11", 14 | 123456: "1.23456", 15 | } 16 | 17 | It("returns proper values", func() { 18 | for t, e := range tests { 19 | Expect(t.AsCpus()).To(Equal(e)) 20 | } 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Circle status](https://circleci.com/gh/rindek/dcc/tree/master.png?style=shield) 2 | 3 | ## Description 4 | 5 | A handy script which quickly converts docker-compose.yml versions to other versions. If you're planning a config upgrade this can spare you some time 6 | 7 | ### Installation 8 | 9 | ``` 10 | go install github.com/rindek/dcc 11 | ``` 12 | 13 | ### Usage 14 | 15 | ``` 16 | dcc v1 v2.3 docker-compose.yml 17 | ``` 18 | 19 | ### List of available conversions 20 | 21 | ``` 22 | dcc a a a 23 | ``` 24 | -------------------------------------------------------------------------------- /functions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func StringArray(str string) []string { 10 | out := []string{str} 11 | 12 | if str == "" { 13 | return nil 14 | } 15 | 16 | return out 17 | } 18 | 19 | func printAndExit(err error) { 20 | fmt.Println(err) 21 | os.Exit(1) 22 | } 23 | 24 | func unknownInputError() error { 25 | convs := getConverters() 26 | 27 | errmsg := "Invalid converter versions, available are:\n" 28 | 29 | for from, tos := range convs { 30 | for to, _ := range tos { 31 | errmsg = errmsg + fmt.Sprintf("\t* %s -> %s\n", from, to) 32 | } 33 | } 34 | 35 | return errors.New(errmsg) 36 | } 37 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | name = "github.com/jessevdk/go-flags" 26 | version = "1.3.0" 27 | 28 | [[constraint]] 29 | name = "github.com/onsi/ginkgo" 30 | version = "1.4.0" 31 | 32 | [[constraint]] 33 | name = "github.com/onsi/gomega" 34 | version = "1.2.0" 35 | 36 | [[constraint]] 37 | branch = "v2" 38 | name = "gopkg.in/yaml.v2" 39 | -------------------------------------------------------------------------------- /convertable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import yaml "gopkg.in/yaml.v2" 4 | 5 | // Convertable parses input as array of bytes into "from" version, then calls 6 | // convert function which needs to be implemented in specific version conversion 7 | // then it marshals yaml into "out" version and returns the array of bytes of 8 | // that yml file 9 | // Args: 10 | // in interface{} - composev1, composev23 etc type 11 | // out interface{} - composev1, composev23 etc type 12 | // bytes - input docker-compose.yml as array of bytes 13 | // f - func which populates out interface{} 14 | // Returns []byte, error 15 | // Example usage: 16 | // return Convertable(&in, &out, bytes, func() { 17 | // out.Version = "2.3" 18 | // }) 19 | func Convertable(in interface{}, out interface{}, bytes *[]byte, f func()) ([]byte, error) { 20 | err := yaml.Unmarshal(*bytes, in) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | f() 26 | 27 | bytesout, err := yaml.Marshal(&out) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return bytesout, nil 33 | } 34 | 35 | type converter map[string]func(bytes *[]byte) ([]byte, error) 36 | 37 | func getConverters() map[string]converter { 38 | var converters = make(map[string]converter) 39 | 40 | converters["v1"] = converter{ 41 | "v2.3": v1tov23, 42 | "v3.2": v1tov32, 43 | } 44 | 45 | return converters 46 | } 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/jessevdk/go-flags" 9 | ) 10 | 11 | const VERSION = "0.2" 12 | 13 | var opts struct { 14 | Args struct { 15 | From string `positional-arg-name:"from" description:"version to convert from"` 16 | To string `positional-arg-name:"to" description:"version to convert to"` 17 | File string `positional-arg-name:"file" description:"docker-compose yaml file"` 18 | } `positional-args:"true" required:"2"` 19 | } 20 | 21 | func printUsage() { 22 | fmt.Println(VERSION) 23 | fmt.Println("") 24 | fmt.Printf("Usage: %s from to file\n", os.Args[0]) 25 | fmt.Println("\tfrom - version to convert from") 26 | fmt.Println("\tto - version to convert to") 27 | fmt.Println("\tfile - path to docker-compose file") 28 | fmt.Println("") 29 | fmt.Printf("Example: %s v1 v3.2 docker-compose.yml\n", os.Args[0]) 30 | } 31 | 32 | func validateInput(from string, to string) error { 33 | convs := getConverters() 34 | 35 | _, ok := convs[from] 36 | if ok { 37 | _, ok := convs[from][to] 38 | if ok { 39 | return nil 40 | } 41 | } 42 | 43 | return unknownInputError() 44 | } 45 | 46 | func main() { 47 | _, err := flags.Parse(&opts) 48 | 49 | if err != nil { 50 | printUsage() 51 | os.Exit(1) 52 | } 53 | 54 | err = validateInput(opts.Args.From, opts.Args.To) 55 | if err != nil { 56 | printAndExit(err) 57 | } 58 | 59 | f, err := loadFile(opts.Args.File) 60 | if err != nil { 61 | printAndExit(err) 62 | } 63 | 64 | if err = convert(opts.Args.From, opts.Args.To, f); err != nil { 65 | printAndExit(err) 66 | } 67 | } 68 | 69 | func convert(from string, to string, compose []byte) error { 70 | f := getConverters()[from][to] 71 | 72 | out, err := f(&compose) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | fmt.Println(string(out)) 78 | return nil 79 | } 80 | 81 | func loadFile(path string) ([]byte, error) { 82 | f, err := ioutil.ReadFile(path) 83 | 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return f, nil 89 | } 90 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/jessevdk/go-flags" 6 | packages = ["."] 7 | revision = "96dc06278ce32a0e9d957d590bb987c81ee66407" 8 | version = "v1.3.0" 9 | 10 | [[projects]] 11 | name = "github.com/onsi/ginkgo" 12 | packages = [".","config","internal/codelocation","internal/containernode","internal/failer","internal/leafnodes","internal/remote","internal/spec","internal/spec_iterator","internal/specrunner","internal/suite","internal/testingtproxy","internal/writer","reporters","reporters/stenographer","reporters/stenographer/support/go-colorable","reporters/stenographer/support/go-isatty","types"] 13 | revision = "9eda700730cba42af70d53180f9dcce9266bc2bc" 14 | version = "v1.4.0" 15 | 16 | [[projects]] 17 | name = "github.com/onsi/gomega" 18 | packages = [".","format","internal/assertion","internal/asyncassertion","internal/oraclematcher","internal/testingtsupport","matchers","matchers/support/goraph/bipartitegraph","matchers/support/goraph/edge","matchers/support/goraph/node","matchers/support/goraph/util","types"] 19 | revision = "c893efa28eb45626cdaa76c9f653b62488858837" 20 | version = "v1.2.0" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "golang.org/x/net" 25 | packages = ["html","html/atom","html/charset"] 26 | revision = "d866cfc389cec985d6fda2859936a575a55a3ab6" 27 | 28 | [[projects]] 29 | branch = "master" 30 | name = "golang.org/x/sys" 31 | packages = ["unix"] 32 | revision = "83801418e1b59fb1880e363299581ee543af32ca" 33 | 34 | [[projects]] 35 | branch = "master" 36 | name = "golang.org/x/text" 37 | packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/utf8internal","language","runes","transform","unicode/cldr"] 38 | revision = "eb22672bea55af56d225d4e35405f4d2e9f062a0" 39 | 40 | [[projects]] 41 | branch = "v2" 42 | name = "gopkg.in/yaml.v2" 43 | packages = ["."] 44 | revision = "287cf08546ab5e7e37d55a84f7ed3fd1db036de5" 45 | 46 | [solve-meta] 47 | analyzer-name = "dep" 48 | analyzer-version = 1 49 | inputs-digest = "3a35ac1be8659252bf4bc6c0dcdf98ee5790f6c6595d42374bded35d0e80e26b" 50 | solver-name = "gps-cdcl" 51 | solver-version = 1 52 | -------------------------------------------------------------------------------- /v1tov23.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func v1tov23(bytes *[]byte) ([]byte, error) { 4 | 5 | var in composev1 6 | var out composev23 7 | 8 | return Convertable(&in, &out, bytes, func() { 9 | out.Version = "2.3" 10 | 11 | out.Services = make(map[string]V23Service) 12 | out.Volumes = make(map[string]V32Volume) 13 | 14 | for k, v := range in { 15 | out.Services[k] = V23Service{ 16 | Build: V23ServiceBuild{ 17 | Context: v.Build, 18 | Dockerfile: v.Dockerfile, 19 | }, 20 | CapAdd: v.CapAdd, 21 | CapDrop: v.CapDrop, 22 | Command: v.Command, 23 | CGroupParent: v.CGroupParent, 24 | ContainerName: v.ContainerName, 25 | Devices: v.Devices, 26 | DNS: v.DNS, 27 | DNSSearch: v.DNSSearch, 28 | Entrypoint: v.Entrypoint, 29 | EnvFile: StringArray(v.EnvFile), 30 | Environment: v.Environment, 31 | Expose: v.Expose, 32 | Extends: v.Extends, 33 | ExternalLinks: v.ExternalLinks, 34 | ExtraHosts: v.ExtraHosts, 35 | Image: v.Image, 36 | Labels: v.Labels, 37 | Links: v.Links, 38 | Logging: V32ServiceLogging{ 39 | Driver: v.LogDriver, 40 | Options: v.LogOpt, 41 | }, 42 | NetworkMode: v.Net, 43 | Pid: v.Pid, 44 | Ports: v.Ports, 45 | SecurityOpt: v.SecurityOpt, 46 | StopSignal: v.StopSignal, 47 | ULimits: v.ULimits, 48 | Volumes: parseVolumesToShortFormat(v.Volumes, &out), 49 | VolumeDriver: v.VolumeDriver, 50 | VolumesFrom: v.VolumesFrom, 51 | 52 | CpuShares: v.CpuShares, 53 | CpuSet: v.CpuSet, 54 | Cpus: v.CpuQuota.AsCpus(), 55 | 56 | User: v.User, 57 | WorkingDir: v.WorkingDir, 58 | DomainName: v.DomainName, 59 | Hostname: v.Hostname, 60 | IPC: v.IPC, 61 | MacAddress: v.MacAddress, 62 | MemLimit: v.MemLimit, 63 | MemSwapLimit: v.MemSwapLimit, 64 | 65 | Privileged: v.Privileged, 66 | Restart: v.Restart, 67 | ReadOnly: v.ReadOnly, 68 | ShmSize: v.ShmSize, 69 | StdinOpen: v.StdinOpen, 70 | TTY: v.TTY, 71 | } 72 | } 73 | }) 74 | } 75 | 76 | func parseVolumesToShortFormat(in []string, out *composev23) []string { 77 | for _, vol := range in { 78 | named := regexpNamedVolume() 79 | 80 | if named.MatchString(vol) { 81 | result := extractRegexpVars(named, &vol) 82 | 83 | out.Volumes[result["volume_name"]] = V32Volume{} 84 | } 85 | } 86 | 87 | // return same array of volumes 88 | return in 89 | } 90 | -------------------------------------------------------------------------------- /v1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type composev1 map[string]V1 8 | type CpuQuota int 9 | 10 | type V1 struct { 11 | Build string `yaml:"build,omitempty"` 12 | Dockerfile string `yaml:"dockerfile,omitempty"` 13 | CapAdd []string `yaml:"cap_add,omitempty"` 14 | CapDrop []string `yaml:"cap_drop,omitempty"` 15 | Command string `yaml:"command,omitempty"` 16 | CGroupParent string `yaml:"cgroup_parent,omitempty"` 17 | ContainerName string `yaml:"container_name,omitempty"` 18 | Devices []string `yaml:"devices,omitempty"` 19 | DNS []string `yaml:"dns,omitempty"` 20 | DNSSearch []string `yaml:"dns_search,omitempty"` 21 | Entrypoint string `yaml:"entrypoint,omitempty"` 22 | EnvFile string `yaml:"env_file,omitempty"` 23 | Environment []string `yaml:"environment,omitempty"` 24 | Expose []string `yaml:"expose,omitempty"` 25 | Extends V1Extends `yaml:"extends,omitempty"` 26 | ExternalLinks []string `yaml:"external_links,omitempty"` 27 | ExtraHosts []string `yaml:"extra_hosts,omitempty"` 28 | Image string `yaml:"image,omitempty"` 29 | Labels []string `yaml:"labels,omitempty"` 30 | Links []string `yaml:"links,omitempty"` 31 | LogDriver string `yaml:"log_driver,omitempty"` 32 | LogOpt map[string]string `yaml:"log_opts,omitempty"` 33 | Net string `yaml:"net,omitempty"` 34 | Pid string `yaml:"pid,omitempty"` 35 | Ports []string `yaml:"ports,omitempty"` 36 | SecurityOpt []string `yaml:"security_opt,omitempty"` 37 | StopSignal string `yaml:"stop_signal,omitempty"` 38 | ULimits V1Ulimits `yaml:"ulimits,omitempty"` 39 | Volumes []string `yaml:"volumes,omitempty"` 40 | VolumeDriver string `yaml:"volume_driver,omitempty"` 41 | VolumesFrom []string `yaml:"volumes_from,omitempty"` 42 | 43 | CpuShares int `yaml:"cpu_shares,omitempty"` 44 | CpuQuota CpuQuota `yaml:"cpu_quota,omitempty"` 45 | CpuSet string `yaml:"cpuset,omitempty"` 46 | User string `yaml:"user,omitempty"` 47 | WorkingDir string `yaml:"working_dir,omitempty"` 48 | DomainName string `yaml:"domainname,omitempty"` 49 | Hostname string `yaml:"hostname,omitempty"` 50 | IPC string `yaml:"ipc,omitempty"` 51 | MacAddress string `yaml:"mac_address,omitempty"` 52 | MemLimit string `yaml:"mem_limit,omitempty"` 53 | MemSwapLimit string `yaml:"memswap_limit,omitempty"` 54 | Privileged bool `yaml:"privileged,omitempty"` 55 | Restart string `yaml:"restart,omitempty"` 56 | ReadOnly bool `yaml:"read_only,omitempty"` 57 | ShmSize string `yaml:"shm_size,omitempty"` 58 | StdinOpen bool `yaml:"stdin_open,omitempty"` 59 | TTY bool `yaml:"tty,omitempty"` 60 | } 61 | 62 | type V1Extends struct { 63 | File string `yaml:"file,omitempty"` 64 | Service string `yaml:"service,omitempty"` 65 | } 66 | 67 | type V1Ulimits struct { 68 | NProc int `yaml:"nproc,omitempty"` 69 | Nofile V1UlimitsNofile `yaml:"nofile,omitempty"` 70 | } 71 | 72 | type V1UlimitsNofile struct { 73 | Soft int `yaml:"soft,omitempty"` 74 | Hard int `yaml:"hard,omitempty"` 75 | } 76 | 77 | func (q *CpuQuota) AsCpus() string { 78 | cpusf := float64(*q) / 100000 79 | cpuss := strconv.FormatFloat(cpusf, 'f', -1, 32) 80 | 81 | if cpuss == "0" { 82 | cpuss = "1.0" 83 | } 84 | 85 | return cpuss 86 | } 87 | -------------------------------------------------------------------------------- /v2.3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type composev23 V23 4 | 5 | type V23 struct { 6 | Version string `yaml:"version"` 7 | Services map[string]V23Service `yaml:"services"` 8 | Networks map[string]V23Network `yaml:"networks,omitempty"` 9 | Volumes map[string]V32Volume `yaml:"volumes,omitempty"` 10 | } 11 | 12 | type V23Network struct { 13 | Driver string `yaml:"driver,omitempty"` 14 | DriverOpts map[string]string `yaml:"driver_opts,omitempty"` 15 | EnableIPv6 bool `yaml:"enable_ipv6,omitempty"` 16 | IPAM V23NetworkIPAM `yaml:"ipam,omitempty"` 17 | Internal bool `yaml:"internal,omitempty"` 18 | Labels []string `yaml:"labels,omitempty"` 19 | External V23ExternalResource `yaml:"external,omitempty"` 20 | } 21 | 22 | type V23ExternalResource struct { 23 | Name string `yaml:"name,omitempty"` 24 | } 25 | 26 | type V23NetworkIPAM struct { 27 | Driver string `yaml:"driver,omitempty"` 28 | Config []V23NetworkIPAMConfig `yaml:"config,omitempty"` 29 | Options map[string]string `yaml:"options,omitempty"` 30 | } 31 | 32 | type V23NetworkIPAMConfig struct { 33 | Subnet string `yaml:"subnet,omitempty"` 34 | IPRange string `yaml:"ip_range,omitempty"` 35 | Gateway string `yaml:"gateway,omitempty"` 36 | AuxAddresses map[string]string `yaml:"aux_addresses,omitempty"` 37 | } 38 | 39 | type V23Service struct { 40 | Build V23ServiceBuild `yaml:"build,omitempty"` 41 | CapAdd []string `yaml:"cap_add,omitempty"` 42 | CapDrop []string `yaml:"cap_drop,omitempty"` 43 | Command string `yaml:"command,omitempty"` 44 | CGroupParent string `yaml:"cgroup_parent,omitempty"` 45 | ContainerName string `yaml:"container_name,omitempty"` 46 | Devices []string `yaml:"devices,omitempty"` 47 | DependsOn []string `yaml:"depends_on,omitempty"` 48 | DNS []string `yaml:"dns,omitempty"` 49 | DNSOpt []string `yaml:"dns_opt,omitempty"` 50 | DNSSearch []string `yaml:"dns_search,omitempty"` 51 | TmpFS []string `yaml:"tmpfs,omitempty"` 52 | Entrypoint string `yaml:"entrypoint,omitempty"` 53 | EnvFile []string `yaml:"env_file,omitempty"` 54 | Environment []string `yaml:"environment,omitempty"` 55 | Expose []string `yaml:"expose,omitempty"` 56 | Extends V1Extends `yaml:"extends,omitempty"` 57 | ExternalLinks []string `yaml:"external_links,omitempty"` 58 | ExtraHosts []string `yaml:"extra_hosts,omitempty"` 59 | GroupAdd []string `yaml:"group_add,omitempty"` 60 | Healthcheck V23ServiceHealthcheck `yaml:"healthcheck,omitempty"` 61 | Image string `yaml:"image,omitempty"` 62 | Init string `yaml:"init,omitempty"` 63 | Isolation string `yaml:"isolation,omitempty"` 64 | Labels []string `yaml:"labels,omitempty"` 65 | Links []string `yaml:"links,omitempty"` 66 | Logging V32ServiceLogging `yaml:"logging,omitempty"` 67 | NetworkMode string `yaml:"network_mode,omitempty"` 68 | Networks []string `yaml:"networks,omitempty"` 69 | Pid string `yaml:"pid,omitempty"` 70 | PidsLimit int `yaml:"pids_limit,omitempty"` 71 | Ports []string `yaml:"ports,omitempty"` 72 | Scale int `yaml:"scale,omitempty"` 73 | SecurityOpt []string `yaml:"security_opt,omitempty"` 74 | StopGracePeriod string `yaml:"stop_grace_period,omitempty"` 75 | StopSignal string `yaml:"stop_signal,omitempty"` 76 | StorageOpt string `yaml:"storage_opt,omitempty"` 77 | SysCtls []string `yaml:"sysctls,omitempty"` 78 | ULimits V1Ulimits `yaml:"ulimits,omitempty"` 79 | UsernsMode string `yaml:"userns_mode,omitempty"` 80 | Volumes []string `yaml:"volumes,omitempty"` 81 | VolumeDriver string `yaml:"volume_driver,omitempty"` 82 | VolumesFrom []string `yaml:"volumes_from,omitempty"` 83 | 84 | Restart string `yaml:"restart,omitempty"` 85 | 86 | CpuCount int `yaml:"cpu_count,omitempty"` 87 | CpuPercent int `yaml:"cpu_percent,omitempty"` 88 | Cpus string `yaml:"cpus,omitempty"` 89 | CpuShares int `yaml:"cpu_shares,omitempty"` 90 | CpuQuota CpuQuota `yaml:"cpu_quota,omitempty"` 91 | CpuSet string `yaml:"cpuset,omitempty"` 92 | 93 | User string `yaml:"user,omitempty"` 94 | WorkingDir string `yaml:"working_dir,omitempty"` 95 | DomainName string `yaml:"domainname,omitempty"` 96 | Hostname string `yaml:"hostname,omitempty"` 97 | IPC string `yaml:"ipc,omitempty"` 98 | MacAddress string `yaml:"mac_address,omitempty"` 99 | MemLimit string `yaml:"mem_limit,omitempty"` 100 | MemSwapLimit string `yaml:"memswap_limit,omitempty"` 101 | MemReservation string `yaml:"mem_reservation,omitempty"` 102 | 103 | Privileged bool `yaml:"privileged,omitempty"` 104 | OOMScoreAdj int `yaml:"oom_score_adj,omitempty"` 105 | OOMKillDisable bool `yaml:"oom_kill_disable,omitempty"` 106 | 107 | ReadOnly bool `yaml:"read_only,omitempty"` 108 | ShmSize string `yaml:"shm_size,omitempty"` 109 | StdinOpen bool `yaml:"stdin_open,omitempty"` 110 | TTY bool `yaml:"tty,omitempty"` 111 | } 112 | 113 | type V23ServiceHealthcheck struct { 114 | Test string `yaml:"test,omitempty"` 115 | Interval string `yaml:"interval,omitempty"` 116 | Timeout string `yaml:"timeout,omitempty"` 117 | Retries int `yaml:"retries,omitempty"` 118 | StartPeriod string `yaml:"start_period,omitempty"` 119 | Disable bool `yaml:"disable,omitempty"` 120 | } 121 | 122 | type V23ServiceBuild struct { 123 | Context string `yaml:"context,omitempty"` 124 | Dockerfile string `yaml:"dockerfile,omitempty"` 125 | Args map[string]string `yaml:"args,omitempty"` 126 | ExtraHosts []string `yaml:"extra_hosts,omitempty"` 127 | Labels []string `yaml:"labels,omitempty"` 128 | Network string `yaml:"network,omitempty"` 129 | ShmSize string `yaml:"shm_size,omitempty"` 130 | Target string `yaml:"target,omitempty"` 131 | } 132 | -------------------------------------------------------------------------------- /v3.2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type composev32 V32 4 | 5 | type V32 struct { 6 | Version string `yaml:"version"` 7 | Services map[string]V32Service `yaml:"services"` 8 | Networks map[string]V32Network `yaml:"networks,omitempty"` 9 | Volumes map[string]V32Volume `yaml:"volumes,omitempty"` 10 | } 11 | 12 | type V32Volume struct { 13 | Driver string `yaml:"driver,omitempty"` 14 | DriverOpts map[string]string `yaml:"driver_opts,omitempty"` 15 | External V32ExternalResource `yaml:"external,omitempty"` 16 | Labels []string `yaml:"labels,omitempty"` 17 | } 18 | 19 | type V32Network struct { 20 | Driver string `yaml:"driver,omitempty"` 21 | DriverOpts map[string]string `yaml:"driver_opts,omitempty"` 22 | EnableIPv6 bool `yaml:"enable_ipv6,omitempty"` 23 | IPAM V32NetworkIPAM `yaml:"ipam,omitempty"` 24 | Internal bool `yaml:"internal,omitempty"` 25 | Labels []string `yaml:"labels,omitempty"` 26 | External V32ExternalResource `yaml:"external,omitempty"` 27 | } 28 | 29 | type V32ExternalResource struct { 30 | Name string `yaml:"name,omitempty"` 31 | } 32 | 33 | type V32NetworkIPAM struct { 34 | Driver string `yaml:"driver,omitempty"` 35 | Config []V32NetworkIPAMConfig `yaml:"config,omitempty"` 36 | } 37 | 38 | type V32NetworkIPAMConfig struct { 39 | Subnet string `yaml:"subnet,omitempty"` 40 | } 41 | 42 | type V32Service struct { 43 | Build V32ServiceBuild `yaml:"build,omitempty"` 44 | CapAdd []string `yaml:"cap_add,omitempty"` 45 | CapDrop []string `yaml:"cap_drop,omitempty"` 46 | Command string `yaml:"command,omitempty"` 47 | CGroupParent string `yaml:"cgroup_parent,omitempty"` 48 | ContainerName string `yaml:"container_name,omitempty"` 49 | Deploy V32ServiceDeploy `yaml:"deploy,omitempty"` 50 | Devices []string `yaml:"devices,omitempty"` 51 | DependsOn []string `yaml:"depends_on,omitempty"` 52 | DNS []string `yaml:"dns,omitempty"` 53 | DNSSearch []string `yaml:"dns_search,omitempty"` 54 | TmpFS []string `yaml:"tmpfs,omitempty"` 55 | Entrypoint string `yaml:"entrypoint,omitempty"` 56 | EnvFile string `yaml:"env_file,omitempty"` 57 | Environment []string `yaml:"environment,omitempty"` 58 | Expose []string `yaml:"expose,omitempty"` 59 | ExternalLinks []string `yaml:"external_links,omitempty"` 60 | ExtraHosts []string `yaml:"extra_hosts,omitempty"` 61 | Healthcheck V32ServiceHealthcheck `yaml:"healthcheck,omitempty"` 62 | Image string `yaml:"image,omitempty"` 63 | Isolation string `yaml:"isolation,omitempty"` 64 | Labels []string `yaml:"labels,omitempty"` 65 | Links []string `yaml:"links,omitempty"` 66 | Logging V32ServiceLogging `yaml:"logging,omitempty"` 67 | NetworkMode string `yaml:"network_mode,omitempty"` 68 | Networks []string `yaml:"networks,omitempty"` 69 | Pid string `yaml:"pid,omitempty"` 70 | Ports []V32ServicePorts `yaml:"ports,omitempty"` 71 | Secrets []V32ServiceSecrets `yaml:"secrets,omitempty"` 72 | SecurityOpt []string `yaml:"security_opt,omitempty"` 73 | StopGracePeriod string `yaml:"stop_grace_period,omitempty"` 74 | StopSignal string `yaml:"stop_signal,omitempty"` 75 | SysCtls []string `yaml:"sysctls,omitempty"` 76 | ULimits V1Ulimits `yaml:"ulimits,omitempty"` 77 | Volumes []V32ServiceVolumes `yaml:"volumes,omitempty"` 78 | 79 | Restart string `yaml:"restart,omitempty"` 80 | User string `yaml:"user,omitempty"` 81 | WorkingDir string `yaml:"working_dir,omitempty"` 82 | DomainName string `yaml:"domainname,omitempty"` 83 | Hostname string `yaml:"hostname,omitempty"` 84 | IPC string `yaml:"ipc,omitempty"` 85 | MacAddress string `yaml:"mac_address,omitempty"` 86 | Privileged bool `yaml:"privileged,omitempty"` 87 | ReadOnly bool `yaml:"read_only,omitempty"` 88 | ShmSize string `yaml:"shm_size,omitempty"` 89 | StdinOpen bool `yaml:"stdin_open,omitempty"` 90 | TTY bool `yaml:"tty,omitempty"` 91 | } 92 | 93 | type V32ServiceVolumes struct { 94 | Type string `yaml:"type,omitempty"` 95 | Source string `yaml:"source,omitempty"` 96 | Target string `yaml:"target,omitempty"` 97 | ReadOnly bool `yaml:"read_only,omitempty"` 98 | Bind V32ServiceVolumesBind `yaml:"bind,omitempty"` 99 | Volume V32ServiceVolumesVolume `yaml:"volume,omitempty"` 100 | } 101 | 102 | type V32ServiceVolumesBind struct { 103 | Propagation string `yaml:"propagation,omitempty"` 104 | } 105 | 106 | type V32ServiceVolumesVolume struct { 107 | Nocopy bool `yaml:"nocopy,omitempty"` 108 | } 109 | 110 | type V32ServiceSecrets struct { 111 | Source string `yaml:"source,omitempty"` 112 | Target string `yaml:"target,omitempty"` 113 | UID string `yaml:"uid,omitempty"` 114 | GID string `yaml:"gid,omitempty"` 115 | Mode int `yaml:"mode,omitempty"` 116 | } 117 | 118 | type V32ServicePorts struct { 119 | Target int `yaml:"target,omitempty"` 120 | Published string `yaml:"published,omitempty"` 121 | Protocol string `yaml:"protocol,omitempty"` 122 | Mode string `yaml:"mode,omitempty"` 123 | } 124 | 125 | type V32ServiceLogging struct { 126 | Driver string `yaml:"driver,omitempty"` 127 | Options map[string]string `yaml:"options,omitempty"` 128 | } 129 | 130 | type V32ServiceHealthcheck struct { 131 | Test string `yaml:"test,omitempty"` 132 | Interval string `yaml:"interval,omitempty"` 133 | Timeout string `yaml:"timeout,omitempty"` 134 | Retries int `yaml:"retries,omitempty"` 135 | Disable bool `yaml:"disable,omitempty"` 136 | } 137 | 138 | type V32ServiceBuild struct { 139 | Context string `yaml:"context,omitempty"` 140 | Dockerfile string `yaml:"dockerfile,omitempty"` 141 | Args map[string]string `yaml:"args,omitempty"` 142 | CacheFrom []string `yaml:"cache_from,omitempty"` 143 | } 144 | 145 | type V32ServiceDeploy struct { 146 | Mode string `yaml:"mode,omitempty"` 147 | Replicas int `yaml:"replicas,omitempty"` 148 | Placement map[string][]string `yaml:"placement,omitempty"` 149 | UpdateConfig V32ServiceDeployUpdateConfig `yaml:"update_config,omitempty"` 150 | Resources V32ServiceDeployResources `yaml:"resources,omitempty"` 151 | RestartPolicy V32ServiceDeployRestartPolicy `yaml:"restart_policy,omitempty"` 152 | Labels []string `yaml:"labels,omitempty"` 153 | } 154 | 155 | type V32ServiceDeployRestartPolicy struct { 156 | Condition string `yaml:"condition,omitempty"` 157 | Delay string `yaml:"delay,omitempty"` 158 | MaxAttempts int `yaml:"max_attempts,omitempty"` 159 | Window string `yaml:"window,omitempty"` 160 | } 161 | 162 | type V32ServiceDeployResources struct { 163 | Limits V32ServiceDeployResourcesTable `yaml:"limits,omitempty"` 164 | Reservations V32ServiceDeployResourcesTable `yaml:"reservations,omitempty"` 165 | } 166 | 167 | type V32ServiceDeployResourcesTable struct { 168 | Cpus string `yaml:"cpus,omitempty"` 169 | Memory string `yaml:"memory,omitempty"` 170 | } 171 | 172 | type V32ServiceDeployUpdateConfig struct { 173 | Parallelism int `yaml:"parallelism,omitempty"` 174 | Delay string `yaml:"delay,omitempty"` 175 | FailureAction string `yaml:"failure_action,omitempty"` 176 | Monitor string `yaml:"monitor,omitempty"` 177 | MaxFailureRatio int `yaml:"max_failure_ratio,omitempty"` 178 | } 179 | -------------------------------------------------------------------------------- /v3.3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type composev33 V33 4 | 5 | type V33 struct { 6 | Version string `yaml:"version"` 7 | Services map[string]V33Service `yaml:"services"` 8 | Networks map[string]V33Network `yaml:"networks,omitempty"` 9 | Volumes map[string]V33Volume `yaml:"volumes,omitempty"` 10 | } 11 | 12 | type V33Volume struct { 13 | Driver string `yaml:"driver,omitempty"` 14 | DriverOpts map[string]string `yaml:"driver_opts,omitempty"` 15 | External V33ExternalResource `yaml:"external,omitempty"` 16 | Labels []string `yaml:"labels,omitempty"` 17 | } 18 | 19 | type V33Network struct { 20 | Driver string `yaml:"driver,omitempty"` 21 | DriverOpts map[string]string `yaml:"driver_opts,omitempty"` 22 | EnableIPv6 bool `yaml:"enable_ipv6,omitempty"` 23 | IPAM V33NetworkIPAM `yaml:"ipam,omitempty"` 24 | Internal bool `yaml:"internal,omitempty"` 25 | Labels []string `yaml:"labels,omitempty"` 26 | External V33ExternalResource `yaml:"external,omitempty"` 27 | } 28 | 29 | type V33ExternalResource struct { 30 | Name string `yaml:"name,omitempty"` 31 | } 32 | 33 | type V33NetworkIPAM struct { 34 | Driver string `yaml:"driver,omitempty"` 35 | Config []V33NetworkIPAMConfig `yaml:"config,omitempty"` 36 | } 37 | 38 | type V33NetworkIPAMConfig struct { 39 | Subnet string `yaml:"subnet,omitempty"` 40 | } 41 | 42 | type V33Service struct { 43 | Build V33ServiceBuild `yaml:"build,omitempty"` 44 | CapAdd []string `yaml:"cap_add,omitempty"` 45 | CapDrop []string `yaml:"cap_drop,omitempty"` 46 | Command string `yaml:"command,omitempty"` 47 | CGroupParent string `yaml:"cgroup_parent,omitempty"` 48 | ContainerName string `yaml:"container_name,omitempty"` 49 | CredentialSpec V33ServiceCredentialSpec `yaml:"credential_spec,omitempty"` 50 | Deploy V33ServiceDeploy `yaml:"deploy,omitempty"` 51 | Devices []string `yaml:"devices,omitempty"` 52 | DependsOn []string `yaml:"depends_on,omitempty"` 53 | DNS []string `yaml:"dns,omitempty"` 54 | DNSSearch []string `yaml:"dns_search,omitempty"` 55 | TmpFS []string `yaml:"tmpfs,omitempty"` 56 | Entrypoint string `yaml:"entrypoint,omitempty"` 57 | EnvFile string `yaml:"env_file,omitempty"` 58 | Environment []string `yaml:"environment,omitempty"` 59 | Expose []string `yaml:"expose,omitempty"` 60 | ExternalLinks []string `yaml:"external_links,omitempty"` 61 | ExtraHosts []string `yaml:"extra_hosts,omitempty"` 62 | Healthcheck V33ServiceHealthcheck `yaml:"healthcheck,omitempty"` 63 | Image string `yaml:"image,omitempty"` 64 | Isolation string `yaml:"isolation,omitempty"` 65 | Labels []string `yaml:"labels,omitempty"` 66 | Links []string `yaml:"links,omitempty"` 67 | Logging V33ServiceLogging `yaml:"logging,omitempty"` 68 | NetworkMode string `yaml:"network_mode,omitempty"` 69 | Networks []string `yaml:"networks,omitempty"` 70 | Pid string `yaml:"pid,omitempty"` 71 | Ports []V33ServicePorts `yaml:"ports,omitempty"` 72 | Secrets []V33ServiceSecrets `yaml:"secrets,omitempty"` 73 | SecurityOpt []string `yaml:"security_opt,omitempty"` 74 | StopGracePeriod string `yaml:"stop_grace_period,omitempty"` 75 | StopSignal string `yaml:"stop_signal,omitempty"` 76 | SysCtls []string `yaml:"sysctls,omitempty"` 77 | ULimits V1Ulimits `yaml:"ulimits,omitempty"` 78 | Volumes []V33ServiceVolumes `yaml:"volumes,omitempty"` 79 | 80 | Restart string `yaml:"restart,omitempty"` 81 | User string `yaml:"user,omitempty"` 82 | WorkingDir string `yaml:"working_dir,omitempty"` 83 | DomainName string `yaml:"domainname,omitempty"` 84 | Hostname string `yaml:"hostname,omitempty"` 85 | IPC string `yaml:"ipc,omitempty"` 86 | MacAddress string `yaml:"mac_address,omitempty"` 87 | Privileged bool `yaml:"privileged,omitempty"` 88 | ReadOnly bool `yaml:"read_only,omitempty"` 89 | ShmSize string `yaml:"shm_size,omitempty"` 90 | StdinOpen bool `yaml:"stdin_open,omitempty"` 91 | TTY bool `yaml:"tty,omitempty"` 92 | } 93 | 94 | type V33ServiceVolumes struct { 95 | Type string `yaml:"type,omitempty"` 96 | Source string `yaml:"source,omitempty"` 97 | Target string `yaml:"target,omitempty"` 98 | ReadOnly bool `yaml:"read_only,omitempty"` 99 | Bind V33ServiceVolumesBind `yaml:"bind,omitempty"` 100 | Volume V33ServiceVolumesVolume `yaml:"volume,omitempty"` 101 | } 102 | 103 | type V33ServiceVolumesBind struct { 104 | Propagation string `yaml:"propagation,omitempty"` 105 | } 106 | 107 | type V33ServiceVolumesVolume struct { 108 | Nocopy bool `yaml:"nocopy,omitempty"` 109 | } 110 | 111 | type V33ServiceSecrets struct { 112 | Source string `yaml:"source,omitempty"` 113 | Target string `yaml:"target,omitempty"` 114 | UID string `yaml:"uid,omitempty"` 115 | GID string `yaml:"gid,omitempty"` 116 | Mode int `yaml:"mode,omitempty"` 117 | } 118 | 119 | type V33ServicePorts struct { 120 | Target int `yaml:"target,omitempty"` 121 | Published string `yaml:"published,omitempty"` 122 | Protocol string `yaml:"protocol,omitempty"` 123 | Mode string `yaml:"mode,omitempty"` 124 | } 125 | 126 | type V33ServiceLogging struct { 127 | Driver string `yaml:"driver,omitempty"` 128 | Options map[string]string `yaml:"options,omitempty"` 129 | } 130 | 131 | type V33ServiceHealthcheck struct { 132 | Test string `yaml:"test,omitempty"` 133 | Interval string `yaml:"interval,omitempty"` 134 | Timeout string `yaml:"timeout,omitempty"` 135 | Retries int `yaml:"retries,omitempty"` 136 | Disable bool `yaml:"disable,omitempty"` 137 | } 138 | 139 | type V33ServiceBuild struct { 140 | Context string `yaml:"context,omitempty"` 141 | Dockerfile string `yaml:"dockerfile,omitempty"` 142 | Args map[string]string `yaml:"args,omitempty"` 143 | CacheFrom []string `yaml:"cache_from,omitempty"` 144 | Labels []string `yaml:"labels,omitempty"` 145 | } 146 | 147 | type V33ServiceCredentialSpec struct { 148 | File string `yaml:"file,omitempty"` 149 | Registry string `yaml:"registry,omitempty"` 150 | } 151 | 152 | type V33ServiceDeploy struct { 153 | Mode string `yaml:"mode,omitempty"` 154 | Replicas int `yaml:"replicas,omitempty"` 155 | Placement map[string][]string `yaml:"placement,omitempty"` 156 | UpdateConfig V33ServiceDeployUpdateConfig `yaml:"update_config,omitempty"` 157 | Resources V33ServiceDeployResources `yaml:"resources,omitempty"` 158 | RestartPolicy V33ServiceDeployRestartPolicy `yaml:"restart_policy,omitempty"` 159 | Labels []string `yaml:"labels,omitempty"` 160 | } 161 | 162 | type V33ServiceDeployRestartPolicy struct { 163 | Condition string `yaml:"condition,omitempty"` 164 | Delay string `yaml:"delay,omitempty"` 165 | MaxAttempts int `yaml:"max_attempts,omitempty"` 166 | Window string `yaml:"window,omitempty"` 167 | } 168 | 169 | type V33ServiceDeployResources struct { 170 | Limits V33ServiceDeployResourcesTable `yaml:"limits,omitempty"` 171 | Reservations V33ServiceDeployResourcesTable `yaml:"reservations,omitempty"` 172 | } 173 | 174 | type V33ServiceDeployResourcesTable struct { 175 | Cpus string `yaml:"cpus,omitempty"` 176 | Memory string `yaml:"memory,omitempty"` 177 | } 178 | 179 | type V33ServiceDeployUpdateConfig struct { 180 | Parallelism int `yaml:"parallelism,omitempty"` 181 | Delay string `yaml:"delay,omitempty"` 182 | FailureAction string `yaml:"failure_action,omitempty"` 183 | Monitor string `yaml:"monitor,omitempty"` 184 | MaxFailureRatio int `yaml:"max_failure_ratio,omitempty"` 185 | } 186 | -------------------------------------------------------------------------------- /v1tov32_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("v1tov32", func() { 9 | Describe("parseRestartPolicy", func() { 10 | 11 | tests := map[string]string{ 12 | "always": "", 13 | "unless-stopped": "", 14 | "no": "none", 15 | "other": "other", 16 | } 17 | 18 | It("returns proper values", func() { 19 | for t, e := range tests { 20 | Expect(parseRestartPolicy(t)).To(Equal(e)) 21 | } 22 | }) 23 | }) 24 | 25 | Describe("getProto", func() { 26 | tests := map[string]string{ 27 | "": "tcp", 28 | "udp": "udp", 29 | } 30 | 31 | It("returns proper values", func() { 32 | for t, e := range tests { 33 | Expect(getProto(t)).To(Equal(e)) 34 | } 35 | }) 36 | }) 37 | 38 | Describe("PortRange validate", func() { 39 | Context("invalid port range", func() { 40 | pr := PortRange{ 41 | Start: 100, 42 | End: 50, 43 | } 44 | 45 | It("does not succeed", func() { 46 | Expect(pr.validate("test")).NotTo(Succeed()) 47 | }) 48 | }) 49 | 50 | Context("valid port range", func() { 51 | pr := PortRange{ 52 | Start: 100, 53 | End: 150, 54 | } 55 | 56 | It("validation succeed", func() { 57 | Expect(pr.validate("test")).To(Succeed()) 58 | }) 59 | }) 60 | }) 61 | 62 | Describe("PortRange validateRange", func() { 63 | Context("invalid range between portranges", func() { 64 | this := PortRange{ 65 | Start: 100, 66 | End: 150, 67 | } 68 | 69 | that := PortRange{ 70 | Start: 100, 71 | End: 151, 72 | } 73 | 74 | It("does not succeed", func() { 75 | Expect(this.validateRange(&that, "")).NotTo(Succeed()) 76 | }) 77 | }) 78 | 79 | Context("valid range between portranges", func() { 80 | this := PortRange{ 81 | Start: 100, 82 | End: 150, 83 | } 84 | 85 | that := PortRange{ 86 | Start: 200, 87 | End: 250, 88 | } 89 | 90 | It("validation does succeed", func() { 91 | Expect(this.validateRange(&that, "")).To(Succeed()) 92 | }) 93 | }) 94 | }) 95 | 96 | Describe("extractRegexpVars", func() { 97 | re := createRegexp(`^(?P[a-z0-9-_]+):(?P.+)$`) 98 | 99 | str := "test:/var/www" 100 | 101 | It("expects to match the test string", func() { 102 | Expect(re.MatchString(str)).To(BeTrue()) 103 | }) 104 | 105 | exp := map[string]string{ 106 | "volume_name": "test", 107 | "container_path": "/var/www", 108 | } 109 | 110 | It("expects to properly extract the vars", func() { 111 | Expect(extractRegexpVars(re, &str)).To(Equal(exp)) 112 | }) 113 | }) 114 | 115 | Describe("parseVolumesToLongFormat", func() { 116 | Context("named volumes", func() { 117 | in := []string{"test:/var"} 118 | v := map[string]V32Volume{} 119 | out := parseVolumesToLongFormat(in, v) 120 | 121 | It("has proper volume defined", func() { 122 | e := []V32ServiceVolumes{ 123 | V32ServiceVolumes{ 124 | Type: "volume", 125 | Source: "test", 126 | Target: "/var", 127 | }, 128 | } 129 | 130 | Expect(out).To(Equal(e)) 131 | }) 132 | 133 | It("has a global volume defined", func() { 134 | Expect(v).NotTo(BeEmpty()) 135 | }) 136 | 137 | It("has proper global volume defined", func() { 138 | e := V32Volume{ 139 | Driver: "local", 140 | External: V32ExternalResource{ 141 | Name: "test", 142 | }, 143 | } 144 | 145 | Expect(v["test"]).To(Equal(e)) 146 | }) 147 | }) 148 | Context("path volumes", func() { 149 | in := []string{"/var/www:/app"} 150 | v := map[string]V32Volume{} 151 | out := parseVolumesToLongFormat(in, v) 152 | 153 | It("has proper volume defined", func() { 154 | e := []V32ServiceVolumes{ 155 | V32ServiceVolumes{ 156 | Type: "bind", 157 | Source: "/var/www", 158 | Target: "/app", 159 | }, 160 | } 161 | 162 | Expect(out).To(Equal(e)) 163 | }) 164 | 165 | It("has no global volume defined", func() { 166 | Expect(v).To(BeEmpty()) 167 | }) 168 | }) 169 | }) 170 | 171 | Describe("parsePortsToLongFormat", func() { 172 | Context("ip, source and target is range", func() { 173 | in := []string{"127.0.0.1:5000-5001:6000-6001"} 174 | out := parsePortsToLongFormat(in) 175 | 176 | It("has proper struct defined", func() { 177 | e := []V32ServicePorts{ 178 | V32ServicePorts{ 179 | Target: 6000, 180 | Published: "127.0.0.1:5000", 181 | Protocol: "tcp", 182 | Mode: "host", 183 | }, 184 | V32ServicePorts{ 185 | Target: 6001, 186 | Published: "127.0.0.1:5001", 187 | Protocol: "tcp", 188 | Mode: "host", 189 | }, 190 | } 191 | 192 | Expect(out).To(Equal(e)) 193 | }) 194 | }) 195 | Context("source and target is range", func() { 196 | in := []string{"5000-5001:6000-6001"} 197 | out := parsePortsToLongFormat(in) 198 | 199 | It("has proper struct defined", func() { 200 | e := []V32ServicePorts{ 201 | V32ServicePorts{ 202 | Target: 6000, 203 | Published: "5000", 204 | Protocol: "tcp", 205 | Mode: "host", 206 | }, 207 | V32ServicePorts{ 208 | Target: 6001, 209 | Published: "5001", 210 | Protocol: "tcp", 211 | Mode: "host", 212 | }, 213 | } 214 | 215 | Expect(out).To(Equal(e)) 216 | }) 217 | }) 218 | Context("single port with ip", func() { 219 | in := []string{"127.0.0.1:5000:6000"} 220 | out := parsePortsToLongFormat(in) 221 | 222 | It("has proper struct defined", func() { 223 | e := []V32ServicePorts{ 224 | V32ServicePorts{ 225 | Target: 6000, 226 | Published: "127.0.0.1:5000", 227 | Protocol: "tcp", 228 | Mode: "host", 229 | }, 230 | } 231 | 232 | Expect(out).To(Equal(e)) 233 | }) 234 | }) 235 | Context("single port without ip", func() { 236 | in := []string{"5000:6000"} 237 | out := parsePortsToLongFormat(in) 238 | 239 | It("has proper struct defined", func() { 240 | e := []V32ServicePorts{ 241 | V32ServicePorts{ 242 | Target: 6000, 243 | Published: "5000", 244 | Protocol: "tcp", 245 | Mode: "host", 246 | }, 247 | } 248 | 249 | Expect(out).To(Equal(e)) 250 | }) 251 | }) 252 | Context("port ranges without publish", func() { 253 | in := []string{"5000-5001"} 254 | out := parsePortsToLongFormat(in) 255 | 256 | It("has proper struct defined", func() { 257 | e := []V32ServicePorts{ 258 | V32ServicePorts{ 259 | Target: 5000, 260 | Published: "", 261 | Protocol: "tcp", 262 | Mode: "host", 263 | }, 264 | V32ServicePorts{ 265 | Target: 5001, 266 | Published: "", 267 | Protocol: "tcp", 268 | Mode: "host", 269 | }, 270 | } 271 | 272 | Expect(out).To(Equal(e)) 273 | }) 274 | }) 275 | Context("just port", func() { 276 | in := []string{"5000"} 277 | out := parsePortsToLongFormat(in) 278 | 279 | It("has proper struct defined", func() { 280 | e := []V32ServicePorts{ 281 | V32ServicePorts{ 282 | Target: 5000, 283 | Published: "", 284 | Protocol: "tcp", 285 | Mode: "host", 286 | }, 287 | } 288 | 289 | Expect(out).To(Equal(e)) 290 | }) 291 | }) 292 | 293 | Context("ip, source and target is range udp", func() { 294 | in := []string{"127.0.0.1:5000-5001:6000-6001/udp"} 295 | out := parsePortsToLongFormat(in) 296 | 297 | It("has proper struct defined", func() { 298 | e := []V32ServicePorts{ 299 | V32ServicePorts{ 300 | Target: 6000, 301 | Published: "127.0.0.1:5000", 302 | Protocol: "udp", 303 | Mode: "host", 304 | }, 305 | V32ServicePorts{ 306 | Target: 6001, 307 | Published: "127.0.0.1:5001", 308 | Protocol: "udp", 309 | Mode: "host", 310 | }, 311 | } 312 | 313 | Expect(out).To(Equal(e)) 314 | }) 315 | }) 316 | Context("source and target is range udp", func() { 317 | in := []string{"5000-5001:6000-6001/udp"} 318 | out := parsePortsToLongFormat(in) 319 | 320 | It("has proper struct defined", func() { 321 | e := []V32ServicePorts{ 322 | V32ServicePorts{ 323 | Target: 6000, 324 | Published: "5000", 325 | Protocol: "udp", 326 | Mode: "host", 327 | }, 328 | V32ServicePorts{ 329 | Target: 6001, 330 | Published: "5001", 331 | Protocol: "udp", 332 | Mode: "host", 333 | }, 334 | } 335 | 336 | Expect(out).To(Equal(e)) 337 | }) 338 | }) 339 | Context("single port with ip udp", func() { 340 | in := []string{"127.0.0.1:5000:6000/udp"} 341 | out := parsePortsToLongFormat(in) 342 | 343 | It("has proper struct defined", func() { 344 | e := []V32ServicePorts{ 345 | V32ServicePorts{ 346 | Target: 6000, 347 | Published: "127.0.0.1:5000", 348 | Protocol: "udp", 349 | Mode: "host", 350 | }, 351 | } 352 | 353 | Expect(out).To(Equal(e)) 354 | }) 355 | }) 356 | Context("single port without ip udp", func() { 357 | in := []string{"5000:6000/udp"} 358 | out := parsePortsToLongFormat(in) 359 | 360 | It("has proper struct defined", func() { 361 | e := []V32ServicePorts{ 362 | V32ServicePorts{ 363 | Target: 6000, 364 | Published: "5000", 365 | Protocol: "udp", 366 | Mode: "host", 367 | }, 368 | } 369 | 370 | Expect(out).To(Equal(e)) 371 | }) 372 | }) 373 | Context("port ranges without publish udp", func() { 374 | in := []string{"5000-5001/udp"} 375 | out := parsePortsToLongFormat(in) 376 | 377 | It("has proper struct defined", func() { 378 | e := []V32ServicePorts{ 379 | V32ServicePorts{ 380 | Target: 5000, 381 | Published: "", 382 | Protocol: "udp", 383 | Mode: "host", 384 | }, 385 | V32ServicePorts{ 386 | Target: 5001, 387 | Published: "", 388 | Protocol: "udp", 389 | Mode: "host", 390 | }, 391 | } 392 | 393 | Expect(out).To(Equal(e)) 394 | }) 395 | }) 396 | Context("just port udp", func() { 397 | in := []string{"5000/udp"} 398 | out := parsePortsToLongFormat(in) 399 | 400 | It("has proper struct defined", func() { 401 | e := []V32ServicePorts{ 402 | V32ServicePorts{ 403 | Target: 5000, 404 | Published: "", 405 | Protocol: "udp", 406 | Mode: "host", 407 | }, 408 | } 409 | 410 | Expect(out).To(Equal(e)) 411 | }) 412 | }) 413 | }) 414 | }) 415 | -------------------------------------------------------------------------------- /v1tov32.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strconv" 7 | ) 8 | 9 | func v1tov32(bytes *[]byte) ([]byte, error) { 10 | var in composev1 11 | var out composev32 12 | 13 | return Convertable(&in, &out, bytes, func() { 14 | 15 | fmt.Println("# NOTE: In v3.2 the following keys are not supported and will be ignored:") 16 | fmt.Println("# \t- extends") 17 | fmt.Println("# \t- volumes_from") 18 | fmt.Println("") 19 | 20 | out.Version = "3.2" 21 | out.Services = make(map[string]V32Service) 22 | out.Volumes = make(map[string]V32Volume) 23 | 24 | for k, v := range in { 25 | out.Services[k] = V32Service{ 26 | Build: V32ServiceBuild{ 27 | Context: v.Build, 28 | Dockerfile: v.Dockerfile, 29 | }, 30 | CapAdd: v.CapAdd, 31 | CapDrop: v.CapDrop, 32 | Command: v.Command, 33 | CGroupParent: v.CGroupParent, 34 | ContainerName: v.ContainerName, 35 | Deploy: V32ServiceDeploy{ 36 | Resources: V32ServiceDeployResources{ 37 | Limits: V32ServiceDeployResourcesTable{ 38 | Memory: v.MemLimit, 39 | Cpus: v.CpuQuota.AsCpus(), 40 | }, 41 | }, 42 | RestartPolicy: V32ServiceDeployRestartPolicy{ 43 | Condition: parseRestartPolicy(v.Restart), 44 | }, 45 | }, 46 | Devices: v.Devices, 47 | DNS: v.DNS, 48 | DNSSearch: v.DNSSearch, 49 | Entrypoint: v.Entrypoint, 50 | EnvFile: v.EnvFile, 51 | Environment: v.Environment, 52 | Expose: v.Expose, 53 | ExternalLinks: v.ExternalLinks, 54 | ExtraHosts: v.ExtraHosts, 55 | Image: v.Image, 56 | Labels: v.Labels, 57 | Links: v.Links, 58 | Logging: V32ServiceLogging{ 59 | Driver: v.LogDriver, 60 | Options: v.LogOpt, 61 | }, 62 | NetworkMode: v.Net, 63 | Pid: v.Pid, 64 | Ports: parsePortsToLongFormat(v.Ports), 65 | SecurityOpt: v.SecurityOpt, 66 | StopSignal: v.StopSignal, 67 | ULimits: v.ULimits, 68 | Volumes: parseVolumesToLongFormat(v.Volumes, out.Volumes), 69 | 70 | Restart: v.Restart, 71 | User: v.User, 72 | WorkingDir: v.WorkingDir, 73 | DomainName: v.DomainName, 74 | Hostname: v.Hostname, 75 | IPC: v.IPC, 76 | MacAddress: v.MacAddress, 77 | Privileged: v.Privileged, 78 | ReadOnly: v.ReadOnly, 79 | ShmSize: v.ShmSize, 80 | StdinOpen: v.StdinOpen, 81 | TTY: v.TTY, 82 | } 83 | } 84 | }) 85 | } 86 | 87 | func extractRegexpVars(reg *regexp.Regexp, str *string) map[string]string { 88 | match := reg.FindStringSubmatch(*str) 89 | result := make(map[string]string) 90 | for i, name := range reg.SubexpNames() { 91 | if i != 0 { 92 | result[name] = match[i] 93 | } 94 | } 95 | 96 | return result 97 | } 98 | 99 | func getProto(proto string) string { 100 | if proto == "" { 101 | return "tcp" 102 | } else { 103 | return proto 104 | } 105 | } 106 | 107 | type PortRange struct { 108 | Start int 109 | End int 110 | } 111 | 112 | func (r *PortRange) validate(msg string) error { 113 | if r.End < r.Start { 114 | return fmt.Errorf("%s port range is out of bounds, start is %d, end is %d", msg, r.Start, r.End) 115 | } 116 | 117 | return nil 118 | } 119 | 120 | func (r *PortRange) validateRange(other *PortRange, sourcePort string) error { 121 | if r.End-r.Start != other.End-other.Start { 122 | return fmt.Errorf("Number of published/target port in range is different, matching port is %s", sourcePort) 123 | } 124 | 125 | return nil 126 | } 127 | 128 | func Atoi(s string) int { 129 | i, err := strconv.Atoi(s) 130 | 131 | if err != nil { 132 | panic(err) 133 | } 134 | 135 | return i 136 | } 137 | 138 | func parseRestartPolicy(in string) string { 139 | switch in { 140 | case "always", "unless-stopped": // "always" means "any" in deploy, which is default, do not return anything 141 | // "unless-stopped" is not handled by "service", defaulting to nil 142 | return "" 143 | case "no": 144 | return "none" 145 | } 146 | 147 | return in 148 | } 149 | 150 | func parseVolumesToLongFormat(in []string, volumes map[string]V32Volume) []V32ServiceVolumes { 151 | var out []V32ServiceVolumes 152 | 153 | for _, vol := range in { 154 | named := regexpNamedVolume() 155 | path := regexpPathVolume() 156 | 157 | if named.MatchString(vol) { 158 | result := extractRegexpVars(named, &vol) 159 | 160 | out = append(out, V32ServiceVolumes{ 161 | Type: "volume", 162 | Source: result["volume_name"], 163 | Target: result["container_path"], 164 | }) 165 | 166 | volumes[result["volume_name"]] = V32Volume{ 167 | Driver: "local", 168 | External: V32ExternalResource{ 169 | Name: result["volume_name"], 170 | }, 171 | } 172 | 173 | } else if path.MatchString(vol) { 174 | result := extractRegexpVars(path, &vol) 175 | 176 | out = append(out, V32ServiceVolumes{ 177 | Type: "bind", 178 | Source: result["host_path"], 179 | Target: result["container_path"], 180 | }) 181 | } 182 | } 183 | 184 | return out 185 | } 186 | 187 | func validateRanges(pub *PortRange, tar *PortRange, sourcePort *string) { 188 | err := func() error { 189 | if e := pub.validate("Published"); e != nil { 190 | return e 191 | } 192 | 193 | if e := tar.validate("Target"); e != nil { 194 | return e 195 | } 196 | 197 | if e := pub.validateRange(tar, *sourcePort); e != nil { 198 | return e 199 | } 200 | return nil 201 | }() 202 | 203 | if err != nil { 204 | printAndExit(err) 205 | } 206 | } 207 | 208 | func parsePortsToLongFormat(in []string) []V32ServicePorts { 209 | var out []V32ServicePorts 210 | 211 | for _, port := range in { 212 | full := regexpIpSourceRangeTargetRange() 213 | ranges := regexpSourceRangeTargetRange() 214 | ipsourcetarget := regexpIpSourceTarget() 215 | sourcetarget := regexpSourceTarget() 216 | rrange := regexpRange() 217 | justport := regexpJustPort() 218 | 219 | if full.MatchString(port) { 220 | result := extractRegexpVars(full, &port) 221 | proto := getProto(result["proto"]) 222 | 223 | publishRange := PortRange{ 224 | Start: Atoi(result["ss"]), 225 | End: Atoi(result["se"]), 226 | } 227 | 228 | targetRange := PortRange{ 229 | Start: Atoi(result["ts"]), 230 | End: Atoi(result["te"]), 231 | } 232 | 233 | validateRanges(&publishRange, &targetRange, &port) 234 | 235 | counter := 0 236 | for i := publishRange.Start; i <= publishRange.End; i++ { 237 | out = append(out, V32ServicePorts{ 238 | Target: targetRange.Start + counter, 239 | Published: result["ip"] + ":" + strconv.Itoa(i), 240 | Protocol: proto, 241 | Mode: "host", 242 | }) 243 | 244 | counter = counter + 1 245 | } 246 | } else if ranges.MatchString(port) { 247 | result := extractRegexpVars(ranges, &port) 248 | proto := getProto(result["proto"]) 249 | 250 | publishRange := PortRange{ 251 | Start: Atoi(result["ss"]), 252 | End: Atoi(result["se"]), 253 | } 254 | 255 | targetRange := PortRange{ 256 | Start: Atoi(result["ts"]), 257 | End: Atoi(result["te"]), 258 | } 259 | 260 | validateRanges(&publishRange, &targetRange, &port) 261 | 262 | counter := 0 263 | for i := publishRange.Start; i <= publishRange.End; i++ { 264 | out = append(out, V32ServicePorts{ 265 | Target: targetRange.Start + counter, 266 | Published: strconv.Itoa(i), 267 | Protocol: proto, 268 | Mode: "host", 269 | }) 270 | 271 | counter = counter + 1 272 | } 273 | } else if ipsourcetarget.MatchString(port) { 274 | result := extractRegexpVars(ipsourcetarget, &port) 275 | proto := getProto(result["proto"]) 276 | 277 | target, _ := strconv.Atoi(result["target"]) 278 | 279 | out = append(out, V32ServicePorts{ 280 | Target: target, 281 | Published: result["ip"] + ":" + result["publish"], 282 | Protocol: proto, 283 | Mode: "host", 284 | }) 285 | } else if sourcetarget.MatchString(port) { 286 | result := extractRegexpVars(sourcetarget, &port) 287 | proto := getProto(result["proto"]) 288 | 289 | target, _ := strconv.Atoi(result["target"]) 290 | 291 | out = append(out, V32ServicePorts{ 292 | Target: target, 293 | Published: result["publish"], 294 | Protocol: proto, 295 | Mode: "host", 296 | }) 297 | } else if rrange.MatchString(port) { 298 | result := extractRegexpVars(rrange, &port) 299 | proto := getProto(result["proto"]) 300 | 301 | targetRange := PortRange{ 302 | Start: Atoi(result["rangestart"]), 303 | End: Atoi(result["rangeend"]), 304 | } 305 | targetRange.validate("Target") 306 | 307 | for i := targetRange.Start; i <= targetRange.End; i++ { 308 | out = append(out, V32ServicePorts{ 309 | Target: i, 310 | Protocol: proto, 311 | Mode: "host", 312 | }) 313 | } 314 | } else if justport.MatchString(port) { 315 | result := extractRegexpVars(justport, &port) 316 | proto := getProto(result["proto"]) 317 | 318 | p, _ := strconv.Atoi(result["port"]) 319 | 320 | out = append(out, V32ServicePorts{ 321 | Target: p, 322 | Mode: "host", 323 | Protocol: proto, 324 | }) 325 | } 326 | } 327 | 328 | return out 329 | } 330 | 331 | func createRegexp(match string) *regexp.Regexp { 332 | reg, err := regexp.Compile(match) 333 | if err != nil { 334 | panic(err) 335 | } 336 | 337 | return reg 338 | } 339 | 340 | func regexpIpSourceRangeTargetRange() *regexp.Regexp { 341 | return createRegexp(`^(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(?P\d+)-(?P\d+):(?P\d+)-(?P\d+)(\/(?P[a-z]+))?$`) 342 | } 343 | 344 | func regexpSourceRangeTargetRange() *regexp.Regexp { 345 | return createRegexp(`^(?P\d+)-(?P\d+):(?P\d+)-(?P\d+)(\/(?P[a-z]+))?$`) 346 | } 347 | 348 | func regexpIpSourceTarget() *regexp.Regexp { 349 | return createRegexp(`^(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(?P\d+):(?P\d+)(\/(?P[a-z]+))?$`) 350 | } 351 | 352 | func regexpSourceTarget() *regexp.Regexp { 353 | return createRegexp(`^(?P\d+):(?P\d+)(\/(?P[a-z]+))?$`) 354 | } 355 | 356 | func regexpRange() *regexp.Regexp { 357 | return createRegexp(`^(?P\d+)-(?P\d+)(\/(?P[a-z]+))?$`) 358 | } 359 | 360 | func regexpJustPort() *regexp.Regexp { 361 | return createRegexp(`^(?P\d+)(\/(?P[a-z]+))?$`) 362 | } 363 | 364 | func regexpNamedVolume() *regexp.Regexp { 365 | return createRegexp(`^(?P[a-z0-9-_]+):(?P.+)$`) 366 | } 367 | 368 | func regexpPathVolume() *regexp.Regexp { 369 | return createRegexp(`^(?P[.\/](.+)):(?P[.\/](.+))$`) 370 | } 371 | --------------------------------------------------------------------------------