Compare commits

...

11 Commits
master ... v0.8

Author SHA1 Message Date
Tõnis Tiigi 6224def4dd
Merge pull request #1042 from tonistiigi/update-buildkit-220403-v0.8
[v0.8] vendor: update buildkit to 10e6f94b
3 years ago
Tõnis Tiigi 5abee699a6
Merge pull request #1022 from crazy-max/v0.8-bake-fix-visited-group
[v0.8] bake: fix skipped group when already visited by another one
3 years ago
Tonis Tiigi 54e497dfba vendor: update buildkit to 10e6f94b
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
3 years ago
Tõnis Tiigi dfbd226285
Merge pull request #1034 from crazy-max/v0.8-update-compose-go
[v0.8] update github.com/compose-spec/compose-go to v1.2.1
3 years ago
CrazyMax a78c2957a2
compose: add test for port mapping
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
(cherry picked from commit 243b428a58)
3 years ago
CrazyMax e80890ec89
update github.com/compose-spec/compose-go to v1.2.1
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
(cherry picked from commit 785dc17f13)
3 years ago
CrazyMax 10fe8f380c
bake: fix skipped group when already visited by another one
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
(cherry picked from commit 0b8dde1071)
3 years ago
Tõnis Tiigi 5fac64c2c4
Merge pull request #1018 from tonistiigi/v0.8-compose-target-dot
[v0.8] bake: allow dot in target names for compose
3 years ago
Tonis Tiigi 24ad37a5d2 bake: allow dot in target names for compose
This is a hotfix for v0.8 to unblock release and
restore backward compatibility. More proper fix
coming later.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
3 years ago
Tõnis Tiigi 106651877d
Merge pull request #1005 from tonistiigi/v0.8-update-fsutil-220315
[v0.8] vendor: update fsutil to 9ed61262
3 years ago
Tonis Tiigi 35bcd88f08 vendor: update fsutil to 9ed61262
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 111ea95629)
3 years ago

@ -28,8 +28,10 @@ var (
httpPrefix = regexp.MustCompile(`^https?://`) httpPrefix = regexp.MustCompile(`^https?://`)
gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`) gitURLPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
validTargetNameChars = `[a-zA-Z0-9_-]+` validTargetNameChars = `[a-zA-Z0-9_-]+`
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`) validTargetNameCharsCompose = `[a-zA-Z0-9._-]+`
targetNamePattern = regexp.MustCompile(`^` + validTargetNameChars + `$`)
targetNamePatternCompose = regexp.MustCompile(`^` + validTargetNameCharsCompose + `$`)
) )
type File struct { type File struct {
@ -412,12 +414,12 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error)
} }
func (c Config) ResolveGroup(name string) []string { func (c Config) ResolveGroup(name string) []string {
return c.group(name, map[string]struct{}{}) return dedupString(c.group(name, map[string][]string{}))
} }
func (c Config) group(name string, visited map[string]struct{}) []string { func (c Config) group(name string, visited map[string][]string) []string {
if _, ok := visited[name]; ok { if _, ok := visited[name]; ok {
return nil return visited[name]
} }
var g *Group var g *Group
for _, group := range c.Groups { for _, group := range c.Groups {
@ -429,7 +431,7 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
if g == nil { if g == nil {
return []string{name} return []string{name}
} }
visited[name] = struct{}{} visited[name] = []string{}
targets := make([]string, 0, len(g.Targets)) targets := make([]string, 0, len(g.Targets))
for _, t := range g.Targets { for _, t := range g.Targets {
tgroup := c.group(t, visited) tgroup := c.group(t, visited)
@ -439,6 +441,7 @@ func (c Config) group(name string, visited map[string]struct{}) []string {
targets = append(targets, t) targets = append(targets, t)
} }
} }
visited[name] = targets
return targets return targets
} }
@ -968,6 +971,13 @@ func validateTargetName(name string) error {
return nil return nil
} }
func validateTargetNameCompose(name string) error {
if !targetNamePatternCompose.MatchString(name) {
return errors.Errorf("only %q are allowed", validTargetNameCharsCompose)
}
return nil
}
func sliceEqual(s1, s2 []string) bool { func sliceEqual(s1, s2 []string) bool {
if len(s1) != len(s2) { if len(s1) != len(s2) {
return false return false

@ -1045,3 +1045,79 @@ func TestTargetName(t *testing.T) {
}) })
} }
} }
func TestNestedGroupsWithSameTarget(t *testing.T) {
ctx := context.TODO()
f := File{
Name: "docker-bake.hcl",
Data: []byte(`
group "a" {
targets = ["b", "c"]
}
group "b" {
targets = ["d"]
}
group "c" {
targets = ["b"]
}
target "d" {
context = "."
dockerfile = "./testdockerfile"
}
group "e" {
targets = ["a", "f"]
}
target "f" {
context = "./foo"
}`)}
cases := []struct {
name string
targets []string
ntargets int
}{
{
name: "a",
targets: []string{"b", "c"},
ntargets: 1,
},
{
name: "b",
targets: []string{"d"},
ntargets: 1,
},
{
name: "c",
targets: []string{"b"},
ntargets: 1,
},
{
name: "d",
targets: []string{"d"},
ntargets: 1,
},
{
name: "e",
targets: []string{"a", "f"},
ntargets: 2,
},
}
for _, tt := range cases {
tt := tt
t.Run(tt.name, func(t *testing.T) {
m, g, err := ReadTargets(ctx, []File{f}, []string{tt.name}, nil, nil)
require.NoError(t, err)
require.Equal(t, 1, len(g))
require.Equal(t, tt.targets, g[0].Targets)
require.Equal(t, tt.ntargets, len(m))
require.Equal(t, ".", *m["d"].Context)
require.Equal(t, "./testdockerfile", *m["d"].Dockerfile)
})
}
}

@ -60,7 +60,7 @@ func ParseCompose(dt []byte) (*Config, error) {
continue continue
} }
if err = validateTargetName(s.Name); err != nil { if err = validateTargetNameCompose(s.Name); err != nil {
return nil, errors.Wrapf(err, "invalid service name %q", s.Name) return nil, errors.Wrapf(err, "invalid service name %q", s.Name)
} }

@ -310,6 +310,27 @@ services:
require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "test"}) require.Equal(t, c.Targets[0].Args, map[string]string{"CT_ECR": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "test"})
} }
func TestPorts(t *testing.T) {
var dt = []byte(`
services:
foo:
build:
context: .
ports:
- 3306:3306
bar:
build:
context: .
ports:
- mode: ingress
target: 3306
published: "3306"
protocol: tcp
`)
_, err := ParseCompose(dt)
require.NoError(t, err)
}
func newBool(val bool) *bool { func newBool(val bool) *bool {
b := val b := val
return &b return &b
@ -330,6 +351,10 @@ func TestServiceName(t *testing.T) {
}, },
{ {
svc: "a.b", svc: "a.b",
wantErr: false,
},
{
svc: "a?b",
wantErr: true, wantErr: true,
}, },
{ {

@ -8,7 +8,7 @@ require (
github.com/bugsnag/panicwrap v1.2.0 // indirect github.com/bugsnag/panicwrap v1.2.0 // indirect
github.com/cenkalti/backoff v2.1.1+incompatible // indirect github.com/cenkalti/backoff v2.1.1+incompatible // indirect
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
github.com/compose-spec/compose-go v1.0.8 github.com/compose-spec/compose-go v1.2.1
github.com/containerd/console v1.0.3 github.com/containerd/console v1.0.3
github.com/containerd/containerd v1.6.1 github.com/containerd/containerd v1.6.1
github.com/docker/cli v20.10.12+incompatible github.com/docker/cli v20.10.12+incompatible
@ -30,7 +30,7 @@ require (
github.com/jinzhu/gorm v1.9.2 // indirect github.com/jinzhu/gorm v1.9.2 // indirect
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
github.com/morikuni/aec v1.0.0 github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5 github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5
@ -42,6 +42,7 @@ require (
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/theupdateframework/notary v0.6.1 // indirect github.com/theupdateframework/notary v0.6.1 // indirect
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 // indirect
github.com/zclconf/go-cty v1.10.0 github.com/zclconf/go-cty v1.10.0
go.opentelemetry.io/otel v1.4.1 go.opentelemetry.io/otel v1.4.1
go.opentelemetry.io/otel/trace v1.4.1 go.opentelemetry.io/otel/trace v1.4.1

@ -280,10 +280,8 @@ github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/compose-spec/compose-go v1.0.8 h1:fgT7mYYu5Sp37i2lUIAAvwJpkAHk6dP5ITHy/LlutUk= github.com/compose-spec/compose-go v1.2.1 h1:8+DAP7Mt/Ohl5y6YbZdilLMvIhMxvuSZcNZyywjQmJE=
github.com/compose-spec/compose-go v1.0.8/go.mod h1:REnCbBugoIdHB7S1sfkN/aJ7AJpNApGNjNiVjA9L8x4= github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
github.com/compose-spec/godotenv v1.1.1 h1:lp+WpAInnw06YN9sV/XLUOV/9z4C+6wjJdWlrdVac7o=
github.com/compose-spec/godotenv v1.1.1/go.mod h1:zF/3BOa18Z24tts5qnO/E9YURQanJTBUf7nlcCTNsyc=
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
@ -966,12 +964,12 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ=
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b h1:plbnJxjht8Z6D3c/ga79D1+VaA/IUfNVp08J3lcDgI8= github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d h1:6pLVBJO3V/lMegbVD5kh2QrpZwqS4ZrxEm/MyifCPaY=
github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b/go.mod h1:WvwAZv8aRScHkqc/+X46cRC2CKMKpqcaX+pRvUTtPes= github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d/go.mod h1:WvwAZv8aRScHkqc/+X46cRC2CKMKpqcaX+pRvUTtPes=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
@ -1286,8 +1284,9 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1
github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo=
github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274 h1:wbyZxD6IPFp0sl5uscMOJRsz5UKGFiNiD16e+MVfKZY=
github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274/go.mod h1:oPAfvw32vlUJSjyDcQ3Bu0nb2ON2B+G0dtVN/SZNJiA= github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274/go.mod h1:oPAfvw32vlUJSjyDcQ3Bu0nb2ON2B+G0dtVN/SZNJiA=
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3 h1:T1pEe+WB3SCPVAfVquvfPfagKZU2Z8c1OP3SuGB+id0=
github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3/go.mod h1:oPAfvw32vlUJSjyDcQ3Bu0nb2ON2B+G0dtVN/SZNJiA=
github.com/tonistiigi/go-actions-cache v0.0.0-20211202175116-9642704158ff/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg= github.com/tonistiigi/go-actions-cache v0.0.0-20211202175116-9642704158ff/go.mod h1:qqvyZqkfwkoJuPU/bw61bItaoO0SJ8YSW0vSVRRvsRg=
github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho= github.com/tonistiigi/go-archvariant v1.0.0/go.mod h1:TxFmO5VS6vMq2kvs3ht04iPXtu2rUT/erOnGFYfk5Ho=
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0=
@ -2041,8 +2040,9 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk=
gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -0,0 +1,23 @@
/*
Copyright 2020 The Compose Specification Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package consts
const (
ComposeProjectName = "COMPOSE_PROJECT_NAME"
ComposePathSeparator = "COMPOSE_PATH_SEPARATOR"
ComposeFilePath = "COMPOSE_FILE"
)

@ -20,4 +20,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -1,4 +1,4 @@
// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) // Package dotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv)
// //
// Examples/readme can be found on the github page at https://github.com/joho/godotenv // Examples/readme can be found on the github page at https://github.com/joho/godotenv
// //
@ -11,7 +11,7 @@
// godotenv.Load() // godotenv.Load()
// //
// and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR") // and all the env vars declared in .env will be available through os.Getenv("SOME_ENV_VAR")
package godotenv package dotenv
import ( import (
"errors" "errors"

@ -1,4 +1,4 @@
package godotenv package dotenv
import ( import (
"bytes" "bytes"
@ -41,9 +41,9 @@ func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
value, ok := lookupFn(key) value, ok := lookupFn(key)
if ok { if ok {
out[key] = value out[key] = value
cutset = left
continue
} }
cutset = left
continue
} }
value, left, err := extractVarValue(left, out, lookupFn) value, left, err := extractVarValue(left, out, lookupFn)
@ -132,14 +132,14 @@ func extractVarValue(src []byte, envMap map[string]string, lookupFn LookupFn) (v
// unquoted value - read until new line // unquoted value - read until new line
end := bytes.IndexFunc(src, isNewLine) end := bytes.IndexFunc(src, isNewLine)
var rest []byte var rest []byte
var value string
if end < 0 { if end < 0 {
value := strings.TrimRightFunc(string(src), unicode.IsSpace) value := strings.Split(string(src), "#")[0] // Remove inline comments on unquoted lines
rest = nil value = strings.TrimRightFunc(value, unicode.IsSpace)
return expandVariables(value, envMap, lookupFn), rest, nil return expandVariables(value, envMap, lookupFn), nil, nil
} }
value = strings.TrimRightFunc(string(src[0:end]), unicode.IsSpace) value := strings.Split(string(src[0:end]), "#")[0]
value = strings.TrimRightFunc(value, unicode.IsSpace)
rest = src[end:] rest = src[end:]
return expandVariables(value, envMap, lookupFn), rest, nil return expandVariables(value, envMap, lookupFn), rest, nil
} }
@ -228,7 +228,6 @@ func isSpace(r rune) bool {
return false return false
} }
// isNewLine reports whether the rune is a new line character // isNewLine reports whether the rune is a new line character
func isNewLine(r rune) bool { func isNewLine(r rune) bool {
return r == '\n' return r == '\n'

@ -115,7 +115,7 @@ func newPathError(path Path, err error) error {
return nil return nil
case *template.InvalidTemplateError: case *template.InvalidTemplateError:
return errors.Errorf( return errors.Errorf(
"invalid interpolation format for %s: %#v. You may need to escape any $ with another $.", "invalid interpolation format for %s: %#v. You may need to escape any $ with another $",
path, err.Template) path, err.Template)
default: default:
return errors.Wrapf(err, "error while interpolating %s", path) return errors.Wrapf(err, "error while interpolating %s", path)

@ -1,3 +1,4 @@
name: Full_Example_project_name
services: services:
foo: foo:
@ -6,6 +7,8 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
args: args:
foo: bar foo: bar
ssh:
- default
target: foo target: foo
network: foo network: foo
cache_from: cache_from:
@ -85,6 +88,10 @@ services:
- spread: node.labels.az - spread: node.labels.az
endpoint_mode: dnsrr endpoint_mode: dnsrr
device_cgroup_rules:
- "c 1:3 mr"
- "a 7:* rmw"
devices: devices:
- "/dev/ttyUSB0:/dev/ttyUSB0" - "/dev/ttyUSB0:/dev/ttyUSB0"

@ -21,7 +21,6 @@ import (
"strings" "strings"
interp "github.com/compose-spec/compose-go/interpolation" interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/types"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -53,7 +52,6 @@ var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
servicePath("oom_score_adj"): toInt64, servicePath("oom_score_adj"): toInt64,
servicePath("pids_limit"): toInt64, servicePath("pids_limit"): toInt64,
servicePath("ports", interp.PathMatchList, "target"): toInt, servicePath("ports", interp.PathMatchList, "target"): toInt,
servicePath("ports", interp.PathMatchList, "published"): toInt,
servicePath("privileged"): toBoolean, servicePath("privileged"): toBoolean,
servicePath("read_only"): toBoolean, servicePath("read_only"): toBoolean,
servicePath("scale"): toInt, servicePath("scale"): toInt,
@ -94,19 +92,11 @@ func toInt64(value string) (interface{}, error) {
} }
func toUnitBytes(value string) (interface{}, error) { func toUnitBytes(value string) (interface{}, error) {
i, err := strconv.ParseInt(value, 10, 64) return transformSize(value)
if err != nil {
return nil, err
}
return types.UnitBytes(i), nil
} }
func toDuration(value string) (interface{}, error) { func toDuration(value string) (interface{}, error) {
i, err := strconv.ParseInt(value, 10, 64) return transformStringToDuration(value)
if err != nil {
return nil, err
}
return types.Duration(i), nil
} }
func toFloat(value string) (interface{}, error) { func toFloat(value string) (interface{}, error) {

@ -23,15 +23,18 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp"
"sort" "sort"
"strconv"
"strings" "strings"
"time" "time"
"github.com/compose-spec/compose-go/consts"
"github.com/compose-spec/compose-go/dotenv"
interp "github.com/compose-spec/compose-go/interpolation" interp "github.com/compose-spec/compose-go/interpolation"
"github.com/compose-spec/compose-go/schema" "github.com/compose-spec/compose-go/schema"
"github.com/compose-spec/compose-go/template" "github.com/compose-spec/compose-go/template"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
"github.com/compose-spec/godotenv"
"github.com/docker/go-units" "github.com/docker/go-units"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -58,8 +61,19 @@ type Options struct {
Interpolate *interp.Options Interpolate *interp.Options
// Discard 'env_file' entries after resolving to 'environment' section // Discard 'env_file' entries after resolving to 'environment' section
discardEnvFiles bool discardEnvFiles bool
// Set project name // Set project projectName
Name string projectName string
// Indicates when the projectName was imperatively set or guessed from path
projectNameImperativelySet bool
}
func (o *Options) SetProjectName(name string, imperativelySet bool) {
o.projectName = normalizeProjectName(name)
o.projectNameImperativelySet = imperativelySet
}
func (o Options) GetProjectName() (string, bool) {
return o.projectName, o.projectNameImperativelySet
} }
// serviceRef identifies a reference to a service. It's used to detect cyclic // serviceRef identifies a reference to a service. It's used to detect cyclic
@ -192,8 +206,17 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
s.EnvFile = newEnvFiles s.EnvFile = newEnvFiles
} }
projectName, projectNameImperativelySet := opts.GetProjectName()
model.Name = normalizeProjectName(model.Name)
if !projectNameImperativelySet && model.Name != "" {
projectName = model.Name
}
if projectName != "" {
configDetails.Environment[consts.ComposeProjectName] = projectName
}
project := &types.Project{ project := &types.Project{
Name: opts.Name, Name: projectName,
WorkingDir: configDetails.WorkingDir, WorkingDir: configDetails.WorkingDir,
Services: model.Services, Services: model.Services,
Networks: model.Networks, Networks: model.Networks,
@ -221,6 +244,13 @@ func Load(configDetails types.ConfigDetails, options ...func(*Options)) (*types.
return project, nil return project, nil
} }
func normalizeProjectName(s string) string {
r := regexp.MustCompile("[a-z0-9_-]")
s = strings.ToLower(s)
s = strings.Join(r.FindAllString(s, -1), "")
return strings.TrimLeft(s, "_-")
}
func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) { func parseConfig(b []byte, opts *Options) (map[string]interface{}, error) {
yml, err := ParseYAML(b) yml, err := ParseYAML(b)
if err != nil { if err != nil {
@ -254,7 +284,14 @@ func loadSections(filename string, config map[string]interface{}, configDetails
cfg := types.Config{ cfg := types.Config{
Filename: filename, Filename: filename,
} }
name := ""
if n, ok := config["name"]; ok {
name, ok = n.(string)
if !ok {
return nil, errors.New("project name must be a string")
}
}
cfg.Name = name
cfg.Services, err = LoadServices(filename, getSection(config, "services"), configDetails.WorkingDir, configDetails.LookupEnv, opts) cfg.Services, err = LoadServices(filename, getSection(config, "services"), configDetails.WorkingDir, configDetails.LookupEnv, opts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -280,10 +317,6 @@ func loadSections(filename string, config map[string]interface{}, configDetails
if len(extensions) > 0 { if len(extensions) > 0 {
cfg.Extensions = extensions cfg.Extensions = extensions
} }
if err != nil {
return nil, err
}
return &cfg, nil return &cfg, nil
} }
@ -357,6 +390,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
reflect.TypeOf(types.DependsOnConfig{}): transformDependsOnConfig, reflect.TypeOf(types.DependsOnConfig{}): transformDependsOnConfig,
reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig, reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig,
reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest, reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest,
reflect.TypeOf(types.SSHConfig{}): transformSSHConfig,
} }
for _, transformer := range additionalTransformers { for _, transformer := range additionalTransformers {
@ -559,7 +593,7 @@ func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, l
return err return err
} }
defer file.Close() defer file.Close()
fileVars, err := godotenv.ParseWithLookup(file, godotenv.LookupFn(lookupEnv)) fileVars, err := dotenv.ParseWithLookup(file, dotenv.LookupFn(lookupEnv))
if err != nil { if err != nil {
return err return err
} }
@ -817,6 +851,10 @@ var transformServicePort TransformerFunc = func(data interface{}) (interface{},
ports = append(ports, v) ports = append(ports, v)
} }
case map[string]interface{}: case map[string]interface{}:
published := value["published"]
if v, ok := published.(int); ok {
value["published"] = strconv.Itoa(v)
}
ports = append(ports, groupXFieldsIntoExtensions(value)) ports = append(ports, groupXFieldsIntoExtensions(value))
default: default:
return data, errors.Errorf("invalid type %T for port", value) return data, errors.Errorf("invalid type %T for port", value)
@ -891,7 +929,7 @@ var transformDependsOnConfig TransformerFunc = func(data interface{}) (interface
for _, serviceIntf := range value { for _, serviceIntf := range value {
service, ok := serviceIntf.(string) service, ok := serviceIntf.(string)
if !ok { if !ok {
return data, errors.Errorf("invalid type %T for service depends_on element. Expected string.", value) return data, errors.Errorf("invalid type %T for service depends_on elementn, expected string", value)
} }
transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted} transformed[service] = map[string]interface{}{"condition": types.ServiceConditionStarted}
} }
@ -941,6 +979,35 @@ var transformServiceNetworkMap TransformerFunc = func(value interface{}) (interf
return value, nil return value, nil
} }
var transformSSHConfig TransformerFunc = func(data interface{}) (interface{}, error) {
switch value := data.(type) {
case map[string]interface{}:
var result []types.SSHKey
for key, val := range value {
if val == nil {
val = ""
}
result = append(result, types.SSHKey{ID: key, Path: val.(string)})
}
return result, nil
case []interface{}:
var result []types.SSHKey
for _, v := range value {
key, val := transformValueToMapEntry(v.(string), "=", false)
result = append(result, types.SSHKey{ID: key, Path: val.(string)})
}
return result, nil
case string:
if value == "" {
value = "default"
}
key, val := transformValueToMapEntry(value, "=", false)
result := []types.SSHKey{{ID: key, Path: val.(string)}}
return result, nil
}
return nil, errors.Errorf("expected a sting, map or a list, got %T: %#v", data, data)
}
var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) { var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) {
list := value.([]interface{}) list := value.([]interface{})
result := make([]string, len(list)) result := make([]string, len(list))
@ -963,47 +1030,52 @@ var transformStringList TransformerFunc = func(data interface{}) (interface{}, e
func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc { func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
return func(data interface{}) (interface{}, error) { return func(data interface{}) (interface{}, error) {
return transformMappingOrList(data, sep, allowNil), nil return transformMappingOrList(data, sep, allowNil)
} }
} }
func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc { func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc {
return func(data interface{}) (interface{}, error) { return func(data interface{}) (interface{}, error) {
return transformListOrMapping(data, sep, allowNil), nil return transformListOrMapping(data, sep, allowNil)
} }
} }
func transformListOrMapping(listOrMapping interface{}, sep string, allowNil bool) interface{} { func transformListOrMapping(listOrMapping interface{}, sep string, allowNil bool) (interface{}, error) {
switch value := listOrMapping.(type) { switch value := listOrMapping.(type) {
case map[string]interface{}: case map[string]interface{}:
return toStringList(value, sep, allowNil) return toStringList(value, sep, allowNil), nil
case []interface{}: case []interface{}:
return listOrMapping return listOrMapping, nil
} }
panic(errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping)) return nil, errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping)
} }
func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) interface{} { func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) (interface{}, error) {
switch value := mappingOrList.(type) { switch value := mappingOrList.(type) {
case map[string]interface{}: case map[string]interface{}:
return toMapStringString(value, allowNil) return toMapStringString(value, allowNil), nil
case []interface{}: case []interface{}:
result := make(map[string]interface{}) result := make(map[string]interface{})
for _, value := range value { for _, value := range value {
parts := strings.SplitN(value.(string), sep, 2) key, val := transformValueToMapEntry(value.(string), sep, allowNil)
key := parts[0] result[key] = val
switch {
case len(parts) == 1 && allowNil:
result[key] = nil
case len(parts) == 1 && !allowNil:
result[key] = ""
default:
result[key] = parts[1]
}
} }
return result return result, nil
}
return nil, errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList)
}
func transformValueToMapEntry(value string, separator string, allowNil bool) (string, interface{}) {
parts := strings.SplitN(value, separator, 2)
key := parts[0]
switch {
case len(parts) == 1 && allowNil:
return key, nil
case len(parts) == 1 && !allowNil:
return key, ""
default:
return key, parts[1]
} }
panic(errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList))
} }
var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) { var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
@ -1045,6 +1117,8 @@ var transformStringToDuration TransformerFunc = func(value interface{}) (interfa
return value, err return value, err
} }
return types.Duration(d), nil return types.Duration(d), nil
case types.Duration:
return value, nil
default: default:
return value, errors.Errorf("invalid type %T for duration", value) return value, errors.Errorf("invalid type %T for duration", value)
} }

@ -53,6 +53,7 @@ func merge(configs []*types.Config) (*types.Config, error) {
base := configs[0] base := configs[0]
for _, override := range configs[1:] { for _, override := range configs[1:] {
var err error var err error
base.Name = mergeNames(base.Name, override.Name)
base.Services, err = mergeServices(base.Services, override.Services) base.Services, err = mergeServices(base.Services, override.Services)
if err != nil { if err != nil {
return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename) return base, errors.Wrapf(err, "cannot merge services from %s", override.Filename)
@ -81,6 +82,13 @@ func merge(configs []*types.Config) (*types.Config, error) {
return base, nil return base, nil
} }
func mergeNames(base, override string) string {
if override != "" {
return override
}
return base
}
func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) { func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
baseServices := mapByName(base) baseServices := mapByName(base)
overrideServices := mapByName(override) overrideServices := mapByName(override)
@ -154,7 +162,7 @@ func toServicePortConfigsMap(s interface{}) (map[interface{}]interface{}, error)
m := map[interface{}]interface{}{} m := map[interface{}]interface{}{}
type port struct { type port struct {
target uint32 target uint32
published uint32 published string
ip string ip string
protocol string protocol string
} }
@ -291,7 +299,7 @@ func mergeLoggingConfig(dst, src reflect.Value) error {
return nil return nil
} }
//nolint: unparam // nolint: unparam
func mergeUlimitsConfig(dst, src reflect.Value) error { func mergeUlimitsConfig(dst, src reflect.Value) error {
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() { if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
dst.Elem().Set(src.Elem()) dst.Elem().Set(src.Elem())
@ -299,7 +307,7 @@ func mergeUlimitsConfig(dst, src reflect.Value) error {
return nil return nil
} }
//nolint: unparam // nolint: unparam
func mergeServiceNetworkConfig(dst, src reflect.Value) error { func mergeServiceNetworkConfig(dst, src reflect.Value) error {
if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() { if src.Interface() != reflect.Zero(reflect.TypeOf(src.Interface())).Interface() {
dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases")) dst.Elem().FieldByName("Aliases").Set(src.Elem().FieldByName("Aliases"))

@ -79,7 +79,7 @@ func normalize(project *types.Project, resolvePaths bool) error {
if resolvePaths { if resolvePaths {
s.Build.Context = localContext s.Build.Context = localContext
} }
} else { // } else {
// might be a remote http/git context. Unfortunately supported "remote" syntax is highly ambiguous // might be a remote http/git context. Unfortunately supported "remote" syntax is highly ambiguous
// in moby/moby and not defined by compose-spec, so let's assume runtime will check // in moby/moby and not defined by compose-spec, so let's assume runtime will check
} }

@ -92,7 +92,7 @@ func populateFieldFromBuffer(char rune, buffer []rune, volume *types.ServiceVolu
volume.Volume = &types.ServiceVolumeVolume{NoCopy: true} volume.Volume = &types.ServiceVolumeVolume{NoCopy: true}
default: default:
if isBindOption(option) { if isBindOption(option) {
volume.Bind = &types.ServiceVolumeBind{Propagation: option} setBindOption(volume, option)
} }
// ignore unknown options // ignore unknown options
} }
@ -109,13 +109,39 @@ var Propagations = []string{
types.PropagationSlave, types.PropagationSlave,
} }
type setBindOptionFunc func(bind *types.ServiceVolumeBind, option string)
var bindOptions = map[string]setBindOptionFunc{
types.PropagationRPrivate: setBindPropagation,
types.PropagationPrivate: setBindPropagation,
types.PropagationRShared: setBindPropagation,
types.PropagationShared: setBindPropagation,
types.PropagationRSlave: setBindPropagation,
types.PropagationSlave: setBindPropagation,
types.SELinuxShared: setBindSELinux,
types.SELinuxPrivate: setBindSELinux,
}
func setBindPropagation(bind *types.ServiceVolumeBind, option string) {
bind.Propagation = option
}
func setBindSELinux(bind *types.ServiceVolumeBind, option string) {
bind.SELinux = option
}
func isBindOption(option string) bool { func isBindOption(option string) bool {
for _, propagation := range Propagations { _, ok := bindOptions[option]
if option == propagation {
return true return ok
} }
func setBindOption(volume *types.ServiceVolumeConfig, option string) {
if volume.Bind == nil {
volume.Bind = &types.ServiceVolumeBind{}
} }
return false
bindOptions[option](volume.Bind, option)
} }
func populateType(volume *types.ServiceVolumeConfig) { func populateType(volume *types.ServiceVolumeConfig) {

@ -8,7 +8,12 @@
"properties": { "properties": {
"version": { "version": {
"type": "string", "type": "string",
"description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file." "description": "declared for backward compatibility, ignored."
},
"name": {
"type": "string",
"description": "define the Compose project name, until user defines one explicitly."
}, },
"services": { "services": {
@ -86,9 +91,13 @@
"context": {"type": "string"}, "context": {"type": "string"},
"dockerfile": {"type": "string"}, "dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}, "args": {"$ref": "#/definitions/list_or_dict"},
"ssh": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"type": "array", "items": {"type": "string"}}, "cache_from": {"type": "array", "items": {"type": "string"}},
"cache_to": {"type": "array", "items": {"type": "string"}},
"no_cache": {"type": "boolean"},
"network": {"type": "string"}, "network": {"type": "string"},
"pull": {"type": "boolean"},
"target": {"type": "string"}, "target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]}, "shm_size": {"type": ["integer", "string"]},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"}, "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
@ -318,7 +327,7 @@
"mode": {"type": "string"}, "mode": {"type": "string"},
"host_ip": {"type": "string"}, "host_ip": {"type": "string"},
"target": {"type": "integer"}, "target": {"type": "integer"},
"published": {"type": "integer"}, "published": {"type": ["string", "integer"]},
"protocol": {"type": "string"} "protocol": {"type": "string"}
}, },
"additionalProperties": false, "additionalProperties": false,
@ -410,7 +419,8 @@
"type": "object", "type": "object",
"properties": { "properties": {
"propagation": {"type": "string"}, "propagation": {"type": "string"},
"create_host_path": {"type": "boolean"} "create_host_path": {"type": "boolean"},
"selinux": {"type": "string", "enum": ["z", "Z"]}
}, },
"additionalProperties": false, "additionalProperties": false,
"patternProperties": {"^x-": {}} "patternProperties": {"^x-": {}}
@ -519,7 +529,8 @@
"type": "object", "type": "object",
"properties": { "properties": {
"cpus": {"type": ["number", "string"]}, "cpus": {"type": ["number", "string"]},
"memory": {"type": "string"} "memory": {"type": "string"},
"pids": {"type": "integer"}
}, },
"additionalProperties": false, "additionalProperties": false,
"patternProperties": {"^x-": {}} "patternProperties": {"^x-": {}}

@ -27,11 +27,6 @@ import (
_ "embed" _ "embed"
) )
const (
defaultVersion = "1.0"
versionField = "version"
)
type portsFormatChecker struct{} type portsFormatChecker struct{}
func (checker portsFormatChecker) IsFormat(input interface{}) bool { func (checker portsFormatChecker) IsFormat(input interface{}) bool {

@ -26,6 +26,7 @@ import (
var delimiter = "\\$" var delimiter = "\\$"
var substitutionNamed = "[_a-z][_a-z0-9]*" var substitutionNamed = "[_a-z][_a-z0-9]*"
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?" var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?"
var patternString = fmt.Sprintf( var patternString = fmt.Sprintf(
@ -60,15 +61,17 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
// It accepts additional substitute function. // It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) { func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
if len(subsFuncs) == 0 { if len(subsFuncs) == 0 {
subsFuncs = []SubstituteFunc{ subsFuncs = getDefaultSortedSubstitutionFunctions(template)
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
} }
var err error var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string { result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
closingBraceIndex := getFirstBraceClosingIndex(substring)
rest := ""
if closingBraceIndex > -1 {
rest = substring[closingBraceIndex+1:]
substring = substring[0 : closingBraceIndex+1]
}
matches := pattern.FindStringSubmatch(substring) matches := pattern.FindStringSubmatch(substring)
groups := matchGroups(matches, pattern) groups := matchGroups(matches, pattern)
if escaped := groups["escaped"]; escaped != "" { if escaped := groups["escaped"]; escaped != "" {
@ -100,7 +103,11 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
if !applied { if !applied {
continue continue
} }
return value interpolatedNested, err := SubstituteWith(rest, mapping, pattern, subsFuncs...)
if err != nil {
return ""
}
return value + interpolatedNested
} }
} }
@ -114,6 +121,42 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
return result, err return result, err
} }
func getDefaultSortedSubstitutionFunctions(template string, fns ...SubstituteFunc) []SubstituteFunc {
hyphenIndex := strings.IndexByte(template, '-')
questionIndex := strings.IndexByte(template, '?')
if hyphenIndex < 0 || hyphenIndex > questionIndex {
return []SubstituteFunc{
requiredNonEmpty,
required,
softDefault,
hardDefault,
}
}
return []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
}
func getFirstBraceClosingIndex(s string) int {
openVariableBraces := 0
for i := 0; i < len(s); i++ {
if s[i] == '}' {
openVariableBraces--
if openVariableBraces == 0 {
return i
}
}
if strings.HasPrefix(s[i:], "${") {
openVariableBraces++
i++
}
}
return -1
}
// Substitute variables in the string with their values // Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) { func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, defaultPattern) return SubstituteWith(template, mapping, defaultPattern)

@ -49,6 +49,7 @@ type ConfigFile struct {
// Config is a full compose file configuration and model // Config is a full compose file configuration and model
type Config struct { type Config struct {
Filename string `yaml:"-" json:"-"` Filename string `yaml:"-" json:"-"`
Name string `yaml:",omitempty" json:"name,omitempty"`
Services Services `json:"services"` Services Services `json:"services"`
Networks Networks `yaml:",omitempty" json:"networks,omitempty"` Networks Networks `yaml:",omitempty" json:"networks,omitempty"`
Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"` Volumes Volumes `yaml:",omitempty" json:"volumes,omitempty"`

@ -29,7 +29,7 @@ import (
// Project is the result of loading a set of compose files // Project is the result of loading a set of compose files
type Project struct { type Project struct {
Name string `yaml:"-" json:"-"` Name string `yaml:"name,omitempty" json:"name,omitempty"`
WorkingDir string `yaml:"-" json:"-"` WorkingDir string `yaml:"-" json:"-"`
Services Services `json:"services"` Services Services `json:"services"`
Networks Networks `yaml:",omitempty" json:"networks,omitempty"` Networks Networks `yaml:",omitempty" json:"networks,omitempty"`

@ -88,88 +88,90 @@ type ServiceConfig struct {
Name string `yaml:"-" json:"-"` Name string `yaml:"-" json:"-"`
Profiles []string `mapstructure:"profiles" yaml:"profiles,omitempty" json:"profiles,omitempty"` Profiles []string `mapstructure:"profiles" yaml:"profiles,omitempty" json:"profiles,omitempty"`
Build *BuildConfig `yaml:",omitempty" json:"build,omitempty"` Build *BuildConfig `yaml:",omitempty" json:"build,omitempty"`
BlkioConfig *BlkioConfig `mapstructure:"blkio_config" yaml:",omitempty" json:"blkio_config,omitempty"` BlkioConfig *BlkioConfig `mapstructure:"blkio_config" yaml:",omitempty" json:"blkio_config,omitempty"`
CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"` CapAdd []string `mapstructure:"cap_add" yaml:"cap_add,omitempty" json:"cap_add,omitempty"`
CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"` CapDrop []string `mapstructure:"cap_drop" yaml:"cap_drop,omitempty" json:"cap_drop,omitempty"`
CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"` CgroupParent string `mapstructure:"cgroup_parent" yaml:"cgroup_parent,omitempty" json:"cgroup_parent,omitempty"`
CPUCount int64 `mapstructure:"cpu_count" yaml:"cpu_count,omitempty" json:"cpu_count,omitempty"` CPUCount int64 `mapstructure:"cpu_count" yaml:"cpu_count,omitempty" json:"cpu_count,omitempty"`
CPUPercent float32 `mapstructure:"cpu_percent" yaml:"cpu_percent,omitempty" json:"cpu_percent,omitempty"` CPUPercent float32 `mapstructure:"cpu_percent" yaml:"cpu_percent,omitempty" json:"cpu_percent,omitempty"`
CPUPeriod int64 `mapstructure:"cpu_period" yaml:"cpu_period,omitempty" json:"cpu_period,omitempty"` CPUPeriod int64 `mapstructure:"cpu_period" yaml:"cpu_period,omitempty" json:"cpu_period,omitempty"`
CPUQuota int64 `mapstructure:"cpu_quota" yaml:"cpu_quota,omitempty" json:"cpu_quota,omitempty"` CPUQuota int64 `mapstructure:"cpu_quota" yaml:"cpu_quota,omitempty" json:"cpu_quota,omitempty"`
CPURTPeriod int64 `mapstructure:"cpu_rt_period" yaml:"cpu_rt_period,omitempty" json:"cpu_rt_period,omitempty"` CPURTPeriod int64 `mapstructure:"cpu_rt_period" yaml:"cpu_rt_period,omitempty" json:"cpu_rt_period,omitempty"`
CPURTRuntime int64 `mapstructure:"cpu_rt_runtime" yaml:"cpu_rt_runtime,omitempty" json:"cpu_rt_runtime,omitempty"` CPURTRuntime int64 `mapstructure:"cpu_rt_runtime" yaml:"cpu_rt_runtime,omitempty" json:"cpu_rt_runtime,omitempty"`
CPUS float32 `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"` CPUS float32 `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
CPUSet string `mapstructure:"cpuset" yaml:"cpuset,omitempty" json:"cpuset,omitempty"` CPUSet string `mapstructure:"cpuset" yaml:"cpuset,omitempty" json:"cpuset,omitempty"`
CPUShares int64 `mapstructure:"cpu_shares" yaml:"cpu_shares,omitempty" json:"cpu_shares,omitempty"` CPUShares int64 `mapstructure:"cpu_shares" yaml:"cpu_shares,omitempty" json:"cpu_shares,omitempty"`
Command ShellCommand `yaml:",omitempty" json:"command,omitempty"` Command ShellCommand `yaml:",omitempty" json:"command,omitempty"`
Configs []ServiceConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"` Configs []ServiceConfigObjConfig `yaml:",omitempty" json:"configs,omitempty"`
ContainerName string `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"` ContainerName string `mapstructure:"container_name" yaml:"container_name,omitempty" json:"container_name,omitempty"`
CredentialSpec *CredentialSpecConfig `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"` CredentialSpec *CredentialSpecConfig `mapstructure:"credential_spec" yaml:"credential_spec,omitempty" json:"credential_spec,omitempty"`
DependsOn DependsOnConfig `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"` DependsOn DependsOnConfig `mapstructure:"depends_on" yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
Deploy *DeployConfig `yaml:",omitempty" json:"deploy,omitempty"` Deploy *DeployConfig `yaml:",omitempty" json:"deploy,omitempty"`
Devices []string `yaml:",omitempty" json:"devices,omitempty"` DeviceCgroupRules []string `mapstructure:"device_cgroup_rules" yaml:"device_cgroup_rules,omitempty" json:"device_cgroup_rules,omitempty"`
DNS StringList `yaml:",omitempty" json:"dns,omitempty"` Devices []string `yaml:",omitempty" json:"devices,omitempty"`
DNSOpts []string `mapstructure:"dns_opt" yaml:"dns_opt,omitempty" json:"dns_opt,omitempty"` DNS StringList `yaml:",omitempty" json:"dns,omitempty"`
DNSSearch StringList `mapstructure:"dns_search" yaml:"dns_search,omitempty" json:"dns_search,omitempty"` DNSOpts []string `mapstructure:"dns_opt" yaml:"dns_opt,omitempty" json:"dns_opt,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"` DNSSearch StringList `mapstructure:"dns_search" yaml:"dns_search,omitempty" json:"dns_search,omitempty"`
DomainName string `mapstructure:"domainname" yaml:"domainname,omitempty" json:"domainname,omitempty"` Dockerfile string `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
Entrypoint ShellCommand `yaml:",omitempty" json:"entrypoint,omitempty"` DomainName string `mapstructure:"domainname" yaml:"domainname,omitempty" json:"domainname,omitempty"`
Environment MappingWithEquals `yaml:",omitempty" json:"environment,omitempty"` Entrypoint ShellCommand `yaml:",omitempty" json:"entrypoint,omitempty"`
EnvFile StringList `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"` Environment MappingWithEquals `yaml:",omitempty" json:"environment,omitempty"`
Expose StringOrNumberList `yaml:",omitempty" json:"expose,omitempty"` EnvFile StringList `mapstructure:"env_file" yaml:"env_file,omitempty" json:"env_file,omitempty"`
Extends ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"` Expose StringOrNumberList `yaml:",omitempty" json:"expose,omitempty"`
ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"` Extends ExtendsConfig `yaml:"extends,omitempty" json:"extends,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` ExternalLinks []string `mapstructure:"external_links" yaml:"external_links,omitempty" json:"external_links,omitempty"`
GroupAdd []string `mapstructure:"group_add" yaml:"group_add,omitempty" json:"group_add,omitempty"` ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
Hostname string `yaml:",omitempty" json:"hostname,omitempty"` GroupAdd []string `mapstructure:"group_add" yaml:"group_add,omitempty" json:"group_add,omitempty"`
HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"` Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
Image string `yaml:",omitempty" json:"image,omitempty"` HealthCheck *HealthCheckConfig `yaml:",omitempty" json:"healthcheck,omitempty"`
Init *bool `yaml:",omitempty" json:"init,omitempty"` Image string `yaml:",omitempty" json:"image,omitempty"`
Ipc string `yaml:",omitempty" json:"ipc,omitempty"` Init *bool `yaml:",omitempty" json:"init,omitempty"`
Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty" json:"isolation,omitempty"` Ipc string `yaml:",omitempty" json:"ipc,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"` Isolation string `mapstructure:"isolation" yaml:"isolation,omitempty" json:"isolation,omitempty"`
Links []string `yaml:",omitempty" json:"links,omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
Logging *LoggingConfig `yaml:",omitempty" json:"logging,omitempty"` CustomLabels Labels `yaml:"-" json:"-"`
LogDriver string `mapstructure:"log_driver" yaml:"log_driver,omitempty" json:"log_driver,omitempty"` Links []string `yaml:",omitempty" json:"links,omitempty"`
LogOpt map[string]string `mapstructure:"log_opt" yaml:"log_opt,omitempty" json:"log_opt,omitempty"` Logging *LoggingConfig `yaml:",omitempty" json:"logging,omitempty"`
MemLimit UnitBytes `mapstructure:"mem_limit" yaml:"mem_limit,omitempty" json:"mem_limit,omitempty"` LogDriver string `mapstructure:"log_driver" yaml:"log_driver,omitempty" json:"log_driver,omitempty"`
MemReservation UnitBytes `mapstructure:"mem_reservation" yaml:"mem_reservation,omitempty" json:"mem_reservation,omitempty"` LogOpt map[string]string `mapstructure:"log_opt" yaml:"log_opt,omitempty" json:"log_opt,omitempty"`
MemSwapLimit UnitBytes `mapstructure:"memswap_limit" yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"` MemLimit UnitBytes `mapstructure:"mem_limit" yaml:"mem_limit,omitempty" json:"mem_limit,omitempty"`
MemSwappiness UnitBytes `mapstructure:"mem_swappiness" yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"` MemReservation UnitBytes `mapstructure:"mem_reservation" yaml:"mem_reservation,omitempty" json:"mem_reservation,omitempty"`
MacAddress string `mapstructure:"mac_address" yaml:"mac_address,omitempty" json:"mac_address,omitempty"` MemSwapLimit UnitBytes `mapstructure:"memswap_limit" yaml:"memswap_limit,omitempty" json:"memswap_limit,omitempty"`
Net string `yaml:"net,omitempty" json:"net,omitempty"` MemSwappiness UnitBytes `mapstructure:"mem_swappiness" yaml:"mem_swappiness,omitempty" json:"mem_swappiness,omitempty"`
NetworkMode string `mapstructure:"network_mode" yaml:"network_mode,omitempty" json:"network_mode,omitempty"` MacAddress string `mapstructure:"mac_address" yaml:"mac_address,omitempty" json:"mac_address,omitempty"`
Networks map[string]*ServiceNetworkConfig `yaml:",omitempty" json:"networks,omitempty"` Net string `yaml:"net,omitempty" json:"net,omitempty"`
OomKillDisable bool `mapstructure:"oom_kill_disable" yaml:"oom_kill_disable,omitempty" json:"oom_kill_disable,omitempty"` NetworkMode string `mapstructure:"network_mode" yaml:"network_mode,omitempty" json:"network_mode,omitempty"`
OomScoreAdj int64 `mapstructure:"oom_score_adj" yaml:"oom_score_adj,omitempty" json:"oom_score_adj,omitempty"` Networks map[string]*ServiceNetworkConfig `yaml:",omitempty" json:"networks,omitempty"`
Pid string `yaml:",omitempty" json:"pid,omitempty"` OomKillDisable bool `mapstructure:"oom_kill_disable" yaml:"oom_kill_disable,omitempty" json:"oom_kill_disable,omitempty"`
PidsLimit int64 `mapstructure:"pids_limit" yaml:"pids_limit,omitempty" json:"pids_limit,omitempty"` OomScoreAdj int64 `mapstructure:"oom_score_adj" yaml:"oom_score_adj,omitempty" json:"oom_score_adj,omitempty"`
Platform string `yaml:",omitempty" json:"platform,omitempty"` Pid string `yaml:",omitempty" json:"pid,omitempty"`
Ports []ServicePortConfig `yaml:",omitempty" json:"ports,omitempty"` PidsLimit int64 `mapstructure:"pids_limit" yaml:"pids_limit,omitempty" json:"pids_limit,omitempty"`
Privileged bool `yaml:",omitempty" json:"privileged,omitempty"` Platform string `yaml:",omitempty" json:"platform,omitempty"`
PullPolicy string `mapstructure:"pull_policy" yaml:"pull_policy,omitempty" json:"pull_policy,omitempty"` Ports []ServicePortConfig `yaml:",omitempty" json:"ports,omitempty"`
ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"` Privileged bool `yaml:",omitempty" json:"privileged,omitempty"`
Restart string `yaml:",omitempty" json:"restart,omitempty"` PullPolicy string `mapstructure:"pull_policy" yaml:"pull_policy,omitempty" json:"pull_policy,omitempty"`
Runtime string `yaml:",omitempty" json:"runtime,omitempty"` ReadOnly bool `mapstructure:"read_only" yaml:"read_only,omitempty" json:"read_only,omitempty"`
Scale int `yaml:"-" json:"-"` Restart string `yaml:",omitempty" json:"restart,omitempty"`
Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"` Runtime string `yaml:",omitempty" json:"runtime,omitempty"`
SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"` Scale int `yaml:"-" json:"-"`
ShmSize UnitBytes `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"` Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"`
StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"` SecurityOpt []string `mapstructure:"security_opt" yaml:"security_opt,omitempty" json:"security_opt,omitempty"`
StopGracePeriod *Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"` ShmSize UnitBytes `mapstructure:"shm_size" yaml:"shm_size,omitempty" json:"shm_size,omitempty"`
StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"` StdinOpen bool `mapstructure:"stdin_open" yaml:"stdin_open,omitempty" json:"stdin_open,omitempty"`
Sysctls Mapping `yaml:",omitempty" json:"sysctls,omitempty"` StopGracePeriod *Duration `mapstructure:"stop_grace_period" yaml:"stop_grace_period,omitempty" json:"stop_grace_period,omitempty"`
Tmpfs StringList `yaml:",omitempty" json:"tmpfs,omitempty"` StopSignal string `mapstructure:"stop_signal" yaml:"stop_signal,omitempty" json:"stop_signal,omitempty"`
Tty bool `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"` Sysctls Mapping `yaml:",omitempty" json:"sysctls,omitempty"`
Ulimits map[string]*UlimitsConfig `yaml:",omitempty" json:"ulimits,omitempty"` Tmpfs StringList `yaml:",omitempty" json:"tmpfs,omitempty"`
User string `yaml:",omitempty" json:"user,omitempty"` Tty bool `mapstructure:"tty" yaml:"tty,omitempty" json:"tty,omitempty"`
UserNSMode string `mapstructure:"userns_mode" yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"` Ulimits map[string]*UlimitsConfig `yaml:",omitempty" json:"ulimits,omitempty"`
Uts string `yaml:"uts,omitempty" json:"uts,omitempty"` User string `yaml:",omitempty" json:"user,omitempty"`
VolumeDriver string `mapstructure:"volume_driver" yaml:"volume_driver,omitempty" json:"volume_driver,omitempty"` UserNSMode string `mapstructure:"userns_mode" yaml:"userns_mode,omitempty" json:"userns_mode,omitempty"`
Volumes []ServiceVolumeConfig `yaml:",omitempty" json:"volumes,omitempty"` Uts string `yaml:"uts,omitempty" json:"uts,omitempty"`
VolumesFrom []string `mapstructure:"volumes_from" yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"` VolumeDriver string `mapstructure:"volume_driver" yaml:"volume_driver,omitempty" json:"volume_driver,omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"` Volumes []ServiceVolumeConfig `yaml:",omitempty" json:"volumes,omitempty"`
VolumesFrom []string `mapstructure:"volumes_from" yaml:"volumes_from,omitempty" json:"volumes_from,omitempty"`
WorkingDir string `mapstructure:"working_dir" yaml:"working_dir,omitempty" json:"working_dir,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"` Extensions map[string]interface{} `yaml:",inline" json:"-"`
} }
@ -292,8 +294,12 @@ type BuildConfig struct {
Context string `yaml:",omitempty" json:"context,omitempty"` Context string `yaml:",omitempty" json:"context,omitempty"`
Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"` Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"` Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
Labels Labels `yaml:",omitempty" json:"labels,omitempty"` Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"` CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
CacheTo StringList `mapstructure:"cache_to" yaml:"cache_to,omitempty" json:"cache_to,omitempty"`
NoCache bool `mapstructure:"no_cache" yaml:"no_cache,omitempty" json:"no_cache,omitempty"`
Pull bool `mapstructure:"pull" yaml:"pull,omitempty" json:"pull,omitempty"`
ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"` ExtraHosts HostsList `mapstructure:"extra_hosts" yaml:"extra_hosts,omitempty" json:"extra_hosts,omitempty"`
Isolation string `yaml:",omitempty" json:"isolation,omitempty"` Isolation string `yaml:",omitempty" json:"isolation,omitempty"`
Network string `yaml:",omitempty" json:"network,omitempty"` Network string `yaml:",omitempty" json:"network,omitempty"`
@ -305,11 +311,11 @@ type BuildConfig struct {
// BlkioConfig define blkio config // BlkioConfig define blkio config
type BlkioConfig struct { type BlkioConfig struct {
Weight uint16 `yaml:",omitempty" json:"weight,omitempty"` Weight uint16 `yaml:",omitempty" json:"weight,omitempty"`
WeightDevice []WeightDevice `yaml:",omitempty" json:"weight_device,omitempty"` WeightDevice []WeightDevice `mapstructure:"weight_device" yaml:",omitempty" json:"weight_device,omitempty"`
DeviceReadBps []ThrottleDevice `yaml:",omitempty" json:"device_read_bps,omitempty"` DeviceReadBps []ThrottleDevice `mapstructure:"device_read_bps" yaml:",omitempty" json:"device_read_bps,omitempty"`
DeviceReadIOps []ThrottleDevice `yaml:",omitempty" json:"device_read_iops,omitempty"` DeviceReadIOps []ThrottleDevice `mapstructure:"device_read_iops" yaml:",omitempty" json:"device_read_iops,omitempty"`
DeviceWriteBps []ThrottleDevice `yaml:",omitempty" json:"device_write_bps,omitempty"` DeviceWriteBps []ThrottleDevice `mapstructure:"device_write_bps" yaml:",omitempty" json:"device_write_bps,omitempty"`
DeviceWriteIOps []ThrottleDevice `yaml:",omitempty" json:"device_write_iops,omitempty"` DeviceWriteIOps []ThrottleDevice `mapstructure:"device_write_iops" yaml:",omitempty" json:"device_write_iops,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"` Extensions map[string]interface{} `yaml:",inline" json:"-"`
} }
@ -423,6 +429,39 @@ func (l Labels) Add(key, value string) Labels {
return l return l
} }
type SSHKey struct {
ID string
Path string
}
// SSHConfig is a mapping type for SSH build config
type SSHConfig []SSHKey
func (s SSHConfig) Get(id string) (string, error) {
for _, sshKey := range s {
if sshKey.ID == id {
return sshKey.Path, nil
}
}
return "", fmt.Errorf("ID %s not found in SSH keys", id)
}
// MarshalYAML makes SSHKey implement yaml.Marshaller
func (s SSHKey) MarshalYAML() (interface{}, error) {
if s.Path == "" {
return s.ID, nil
}
return fmt.Sprintf("%s: %s", s.ID, s.Path), nil
}
// MarshalJSON makes SSHKey implement json.Marshaller
func (s SSHKey) MarshalJSON() ([]byte, error) {
if s.Path == "" {
return []byte(fmt.Sprintf(`"%s"`, s.ID)), nil
}
return []byte(fmt.Sprintf(`"%s": %s`, s.ID, s.Path)), nil
}
// MappingWithColon is a mapping type that can be converted from a list of // MappingWithColon is a mapping type that can be converted from a list of
// 'key: value' strings // 'key: value' strings
type MappingWithColon map[string]string type MappingWithColon map[string]string
@ -493,6 +532,7 @@ type Resource struct {
// TODO: types to convert from units and ratios // TODO: types to convert from units and ratios
NanoCPUs string `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"` NanoCPUs string `mapstructure:"cpus" yaml:"cpus,omitempty" json:"cpus,omitempty"`
MemoryBytes UnitBytes `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"` MemoryBytes UnitBytes `mapstructure:"memory" yaml:"memory,omitempty" json:"memory,omitempty"`
PIds int64 `mapstructure:"pids" yaml:"pids,omitempty" json:"pids,omitempty"`
Devices []DeviceRequest `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"` Devices []DeviceRequest `mapstructure:"devices" yaml:"devices,omitempty" json:"devices,omitempty"`
GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"` GenericResources []GenericResource `mapstructure:"generic_resources" yaml:"generic_resources,omitempty" json:"generic_resources,omitempty"`
@ -579,7 +619,7 @@ type ServicePortConfig struct {
Mode string `yaml:",omitempty" json:"mode,omitempty"` Mode string `yaml:",omitempty" json:"mode,omitempty"`
HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"` HostIP string `mapstructure:"host_ip" yaml:"host_ip,omitempty" json:"host_ip,omitempty"`
Target uint32 `yaml:",omitempty" json:"target,omitempty"` Target uint32 `yaml:",omitempty" json:"target,omitempty"`
Published uint32 `yaml:",omitempty" json:"published,omitempty"` Published string `yaml:",omitempty" json:"published,omitempty"`
Protocol string `yaml:",omitempty" json:"protocol,omitempty"` Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"` Extensions map[string]interface{} `yaml:",inline" json:"-"`
@ -613,21 +653,13 @@ func ParsePortConfig(value string) ([]ServicePortConfig, error) {
func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) { func convertPortToPortConfig(port nat.Port, portBindings map[nat.Port][]nat.PortBinding) ([]ServicePortConfig, error) {
var portConfigs []ServicePortConfig var portConfigs []ServicePortConfig
for _, binding := range portBindings[port] { for _, binding := range portBindings[port] {
startHostPort, endHostPort, err := nat.ParsePortRange(binding.HostPort) portConfigs = append(portConfigs, ServicePortConfig{
HostIP: binding.HostIP,
if err != nil && binding.HostPort != "" { Protocol: strings.ToLower(port.Proto()),
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port()) Target: uint32(port.Int()),
} Published: binding.HostPort,
Mode: "ingress",
for i := startHostPort; i <= endHostPort; i++ { })
portConfigs = append(portConfigs, ServicePortConfig{
HostIP: binding.HostIP,
Protocol: strings.ToLower(port.Proto()),
Target: uint32(port.Int()),
Published: uint32(i),
Mode: "ingress",
})
}
} }
return portConfigs, nil return portConfigs, nil
} }
@ -655,16 +687,30 @@ const (
VolumeTypeTmpfs = "tmpfs" VolumeTypeTmpfs = "tmpfs"
// VolumeTypeNamedPipe is the type for mounting Windows named pipes // VolumeTypeNamedPipe is the type for mounting Windows named pipes
VolumeTypeNamedPipe = "npipe" VolumeTypeNamedPipe = "npipe"
// SElinuxShared share the volume content
SElinuxShared = "z"
// SElinuxUnshared label content as private unshared
SElinuxUnshared = "Z"
) )
// ServiceVolumeBind are options for a service volume of type bind // ServiceVolumeBind are options for a service volume of type bind
type ServiceVolumeBind struct { type ServiceVolumeBind struct {
SELinux string `mapstructure:"selinux" yaml:",omitempty" json:"selinux,omitempty"`
Propagation string `yaml:",omitempty" json:"propagation,omitempty"` Propagation string `yaml:",omitempty" json:"propagation,omitempty"`
CreateHostPath bool `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"` CreateHostPath bool `mapstructure:"create_host_path" yaml:"create_host_path,omitempty" json:"create_host_path,omitempty"`
Extensions map[string]interface{} `yaml:",inline" json:"-"` Extensions map[string]interface{} `yaml:",inline" json:"-"`
} }
// SELinux represents the SELinux re-labeling options.
const (
// SELinuxShared option indicates that the bind mount content is shared among multiple containers
SELinuxShared string = "z"
// SELinuxPrivate option indicates that the bind mount content is private and unshared
SELinuxPrivate string = "Z"
)
// Propagation represents the propagation of a mount. // Propagation represents the propagation of a mount.
const ( const (
// PropagationRPrivate RPRIVATE // PropagationRPrivate RPRIVATE

@ -1,27 +0,0 @@
ARG GOLANG_VERSION=1.17.1
ARG ALPINE_VERSION=3.14
FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION}
WORKDIR /code
code:
FROM +base
COPY . .
golangci:
ARG GOLANGCI_VERSION=v1.40.1
FROM golangci/golangci-lint:${GOLANGCI_VERSION}-alpine
SAVE ARTIFACT /usr/bin/golangci-lint
lint:
FROM +code
COPY +golangci/golangci-lint /usr/bin/golangci-lint
RUN golangci-lint run --timeout 5m ./...
test:
FROM +code
RUN go test ./...
all:
BUILD +lint
BUILD +test

@ -1,18 +0,0 @@
# GoDotEnv
A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file)
From the original Library:
> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environmentssuch as resource handles for databases or credentials for external servicesshould be extracted from the code into environment variables.
>
> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped.
This is a fork of [joho/godotenv](https://github.com/joho/godotenv) focussing on `.env` file support by the compose specification
To run linter and tests, please install [Earthly](https://earthly.dev/get-earthly) and run:
```sh
earthly +all
```

@ -1,3 +0,0 @@
module github.com/compose-spec/godotenv
go 1.16

@ -1,3 +1,7 @@
## 1.4.3
* Fix cases where `json.Number` didn't decode properly [GH-261]
## 1.4.2 ## 1.4.2
* Custom name matchers to support any sort of casing, formatting, etc. for * Custom name matchers to support any sort of casing, formatting, etc. for

@ -684,16 +684,12 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
} }
case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number":
jn := data.(json.Number) jn := data.(json.Number)
i, err := jn.Int64() i, err := strconv.ParseUint(string(jn), 0, 64)
if err != nil { if err != nil {
return fmt.Errorf( return fmt.Errorf(
"error decoding json.Number into %s: %s", name, err) "error decoding json.Number into %s: %s", name, err)
} }
if i < 0 && !d.config.WeaklyTypedInput { val.SetUint(i)
return fmt.Errorf("cannot parse '%s', %d overflows uint",
name, i)
}
val.SetUint(uint64(i))
default: default:
return fmt.Errorf( return fmt.Errorf(
"'%s' expected type '%s', got unconvertible type '%s', value: '%v'", "'%s' expected type '%s', got unconvertible type '%s', value: '%v'",

@ -0,0 +1,32 @@
package imageutil
import (
"encoding/base64"
"encoding/json"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/pkg/errors"
)
// BuildInfo returns build info from image config.
func BuildInfo(dt []byte) (*binfotypes.BuildInfo, error) {
if len(dt) == 0 {
return nil, nil
}
var config binfotypes.ImageConfig
if err := json.Unmarshal(dt, &config); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal image config")
}
if len(config.BuildInfo) == 0 {
return nil, nil
}
dtbi, err := base64.StdEncoding.DecodeString(config.BuildInfo)
if err != nil {
return nil, err
}
var bi binfotypes.BuildInfo
if err = json.Unmarshal(dtbi, &bi); err != nil {
return nil, errors.Wrap(err, "failed to decode buildinfo from image config")
}
return &bi, nil
}

@ -256,7 +256,7 @@ func (p *textMux) print(t *trace) {
} }
// make any open vertex active // make any open vertex active
for dgst, v := range t.byDigest { for dgst, v := range t.byDigest {
if v.isStarted() && !v.isCompleted() { if v.isStarted() && !v.isCompleted() && v.ProgressGroup == nil && !v.hidden {
p.printVtx(t, dgst) p.printVtx(t, dgst)
return return
} }
@ -281,6 +281,10 @@ func (p *textMux) print(t *trace) {
if !ok { if !ok {
continue continue
} }
if v.lastBlockTime == nil {
// shouldn't happen, but not worth crashing over
continue
}
tm := now.Sub(*v.lastBlockTime) tm := now.Sub(*v.lastBlockTime)
speed := float64(v.count) / tm.Seconds() speed := float64(v.count) / tm.Seconds()
overLimit := tm > maxDelay && dgst != current overLimit := tm > maxDelay && dgst != current

@ -123,7 +123,13 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
return nil return nil
} }
var dir visitedDir var (
dir visitedDir
isDir bool
)
if fi != nil {
isDir = fi.IsDir()
}
if includeMatcher != nil || excludeMatcher != nil { if includeMatcher != nil || excludeMatcher != nil {
for len(parentDirs) != 0 { for len(parentDirs) != 0 {
@ -134,7 +140,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
parentDirs = parentDirs[:len(parentDirs)-1] parentDirs = parentDirs[:len(parentDirs)-1]
} }
if fi.IsDir() { if isDir {
dir = visitedDir{ dir = visitedDir{
fi: fi, fi: fi,
path: path, path: path,
@ -156,12 +162,12 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
return errors.Wrap(err, "failed to match includepatterns") return errors.Wrap(err, "failed to match includepatterns")
} }
if fi.IsDir() { if isDir {
dir.includeMatchInfo = matchInfo dir.includeMatchInfo = matchInfo
} }
if !m { if !m {
if fi.IsDir() && onlyPrefixIncludes { if isDir && onlyPrefixIncludes {
// Optimization: we can skip walking this dir if no include // Optimization: we can skip walking this dir if no include
// patterns could match anything inside it. // patterns could match anything inside it.
dirSlash := path + string(filepath.Separator) dirSlash := path + string(filepath.Separator)
@ -191,12 +197,12 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
return errors.Wrap(err, "failed to match excludepatterns") return errors.Wrap(err, "failed to match excludepatterns")
} }
if fi.IsDir() { if isDir {
dir.excludeMatchInfo = matchInfo dir.excludeMatchInfo = matchInfo
} }
if m { if m {
if fi.IsDir() && onlyPrefixExcludeExceptions { if isDir && onlyPrefixExcludeExceptions {
// Optimization: we can skip walking this dir if no // Optimization: we can skip walking this dir if no
// exceptions to exclude patterns could match anything // exceptions to exclude patterns could match anything
// inside it. // inside it.
@ -230,7 +236,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
if includeMatcher != nil || excludeMatcher != nil { if includeMatcher != nil || excludeMatcher != nil {
defer func() { defer func() {
if fi.IsDir() { if isDir {
parentDirs = append(parentDirs, dir) parentDirs = append(parentDirs, dir)
} }
}() }()

13
vendor/modules.txt vendored

@ -32,16 +32,16 @@ github.com/cenkalti/backoff/v4
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e # github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e
## explicit ## explicit
# github.com/compose-spec/compose-go v1.0.8 # github.com/compose-spec/compose-go v1.2.1
## explicit ## explicit
github.com/compose-spec/compose-go/consts
github.com/compose-spec/compose-go/dotenv
github.com/compose-spec/compose-go/errdefs github.com/compose-spec/compose-go/errdefs
github.com/compose-spec/compose-go/interpolation github.com/compose-spec/compose-go/interpolation
github.com/compose-spec/compose-go/loader github.com/compose-spec/compose-go/loader
github.com/compose-spec/compose-go/schema github.com/compose-spec/compose-go/schema
github.com/compose-spec/compose-go/template github.com/compose-spec/compose-go/template
github.com/compose-spec/compose-go/types github.com/compose-spec/compose-go/types
# github.com/compose-spec/godotenv v1.1.1
github.com/compose-spec/godotenv
# github.com/containerd/console v1.0.3 # github.com/containerd/console v1.0.3
## explicit ## explicit
github.com/containerd/console github.com/containerd/console
@ -292,9 +292,9 @@ github.com/matttproud/golang_protobuf_extensions/pbutil
github.com/miekg/pkcs11 github.com/miekg/pkcs11
# github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 # github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
github.com/mitchellh/go-wordwrap github.com/mitchellh/go-wordwrap
# github.com/mitchellh/mapstructure v1.4.2 # github.com/mitchellh/mapstructure v1.4.3
github.com/mitchellh/mapstructure github.com/mitchellh/mapstructure
# github.com/moby/buildkit v0.10.0-rc2.0.20220308185020-fdecd0ae108b # github.com/moby/buildkit v0.10.1-0.20220403220257-10e6f94bf90d
## explicit ## explicit
github.com/moby/buildkit/api/services/control github.com/moby/buildkit/api/services/control
github.com/moby/buildkit/api/types github.com/moby/buildkit/api/types
@ -436,7 +436,8 @@ github.com/theupdateframework/notary/tuf/data
github.com/theupdateframework/notary/tuf/signed github.com/theupdateframework/notary/tuf/signed
github.com/theupdateframework/notary/tuf/utils github.com/theupdateframework/notary/tuf/utils
github.com/theupdateframework/notary/tuf/validation github.com/theupdateframework/notary/tuf/validation
# github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274 # github.com/tonistiigi/fsutil v0.0.0-20220315205639-9ed612626da3
## explicit
github.com/tonistiigi/fsutil github.com/tonistiigi/fsutil
github.com/tonistiigi/fsutil/types github.com/tonistiigi/fsutil/types
# github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea # github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea

Loading…
Cancel
Save