mirror of https://github.com/docker/buildx.git
Merge pull request #1069 from crazy-max/compose-build-secrets
bake: support compose build secrets
This commit is contained in:
commit
a2d5bc7cca
|
@ -74,6 +74,16 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||
dockerfilePath := s.Build.Dockerfile
|
||||
dockerfilePathP = &dockerfilePath
|
||||
}
|
||||
|
||||
var secrets []string
|
||||
for _, bs := range s.Build.Secrets {
|
||||
secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
|
||||
g.Targets = append(g.Targets, s.Name)
|
||||
t := &Target{
|
||||
Name: s.Name,
|
||||
|
@ -89,6 +99,7 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||
})),
|
||||
CacheFrom: s.Build.CacheFrom,
|
||||
NetworkMode: &s.Build.Network,
|
||||
Secrets: secrets,
|
||||
}
|
||||
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
||||
return nil, err
|
||||
|
@ -209,3 +220,21 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
||||
// csv format.
|
||||
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
||||
if psecret.External.External {
|
||||
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
||||
}
|
||||
|
||||
var bkattrs []string
|
||||
if inp.Source != "" {
|
||||
bkattrs = append(bkattrs, "id="+inp.Source)
|
||||
}
|
||||
if psecret.File != "" {
|
||||
bkattrs = append(bkattrs, "src="+psecret.File)
|
||||
}
|
||||
|
||||
return strings.Join(bkattrs, ","), nil
|
||||
}
|
||||
|
|
|
@ -23,6 +23,13 @@ services:
|
|||
none
|
||||
args:
|
||||
buildno: 123
|
||||
secrets:
|
||||
- ENV_TOKEN
|
||||
- aws
|
||||
secrets:
|
||||
ENV_TOKEN: {}
|
||||
aws:
|
||||
file: /root/.aws/credentials
|
||||
`)
|
||||
|
||||
c, err := ParseCompose(dt)
|
||||
|
@ -46,6 +53,10 @@ services:
|
|||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||
require.Equal(t, "none", *c.Targets[1].NetworkMode)
|
||||
require.Equal(t, []string{
|
||||
"id=ENV_TOKEN",
|
||||
"id=aws,src=/root/.aws/credentials",
|
||||
}, c.Targets[1].Secrets)
|
||||
}
|
||||
|
||||
func TestNoBuildOutOfTreeService(t *testing.T) {
|
||||
|
|
2
go.mod
2
go.mod
|
@ -8,7 +8,7 @@ require (
|
|||
github.com/bugsnag/panicwrap v1.2.0 // indirect
|
||||
github.com/cenkalti/backoff v2.1.1+incompatible // indirect
|
||||
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
|
||||
github.com/compose-spec/compose-go v1.2.1
|
||||
github.com/compose-spec/compose-go v1.2.4
|
||||
github.com/containerd/console v1.0.3
|
||||
github.com/containerd/containerd v1.6.3-0.20220401172941-5ff8fce1fcc6
|
||||
github.com/docker/cli v20.10.13+incompatible
|
||||
|
|
4
go.sum
4
go.sum
|
@ -280,8 +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/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/compose-spec/compose-go v1.2.1 h1:8+DAP7Mt/Ohl5y6YbZdilLMvIhMxvuSZcNZyywjQmJE=
|
||||
github.com/compose-spec/compose-go v1.2.1/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
|
||||
github.com/compose-spec/compose-go v1.2.4 h1:nzTFqM8+2J7Veao5Pq5U451thinv3U1wChIvcjX59/A=
|
||||
github.com/compose-spec/compose-go v1.2.4/go.mod h1:pAy7Mikpeft4pxkFU565/DRHEbDfR84G6AQuiL+Hdg8=
|
||||
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-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
|
|
|
@ -15,6 +15,13 @@ services:
|
|||
- foo
|
||||
- bar
|
||||
labels: [FOO=BAR]
|
||||
secrets:
|
||||
- secret1
|
||||
- source: secret2
|
||||
target: my_secret
|
||||
uid: '103'
|
||||
gid: '103'
|
||||
mode: 0440
|
||||
|
||||
|
||||
cap_add:
|
||||
|
|
|
@ -53,6 +53,8 @@ type Options struct {
|
|||
SkipNormalization bool
|
||||
// Resolve paths
|
||||
ResolvePaths bool
|
||||
// Convert Windows paths
|
||||
ConvertWindowsPaths bool
|
||||
// Skip consistency check
|
||||
SkipConsistencyCheck bool
|
||||
// Skip extends
|
||||
|
@ -489,7 +491,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
|||
return nil, fmt.Errorf("cannot extend service %q in %s: service not found", name, filename)
|
||||
}
|
||||
|
||||
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths)
|
||||
serviceConfig, err := LoadService(name, target.(map[string]interface{}), workingDir, lookupEnv, opts.ResolvePaths, opts.ConvertWindowsPaths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -552,7 +554,7 @@ func loadServiceWithExtends(filename, name string, servicesDict map[string]inter
|
|||
|
||||
// LoadService produces a single ServiceConfig from a compose file Dict
|
||||
// the serviceDict is not validated if directly used. Use Load() to enable validation
|
||||
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool) (*types.ServiceConfig, error) {
|
||||
func LoadService(name string, serviceDict map[string]interface{}, workingDir string, lookupEnv template.Mapping, resolvePaths bool, convertPaths bool) (*types.ServiceConfig, error) {
|
||||
serviceConfig := &types.ServiceConfig{
|
||||
Scale: 1,
|
||||
}
|
||||
|
@ -577,11 +579,30 @@ func LoadService(name string, serviceDict map[string]interface{}, workingDir str
|
|||
if resolvePaths {
|
||||
serviceConfig.Volumes[i] = resolveVolumePath(volume, workingDir, lookupEnv)
|
||||
}
|
||||
|
||||
if convertPaths {
|
||||
serviceConfig.Volumes[i] = convertVolumePath(volume)
|
||||
}
|
||||
}
|
||||
|
||||
return serviceConfig, nil
|
||||
}
|
||||
|
||||
// Windows paths, c:\\my\\path\\shiny, need to be changed to be compatible with
|
||||
// the Engine. Volume paths are expected to be linux style /c/my/path/shiny/
|
||||
func convertVolumePath(volume types.ServiceVolumeConfig) types.ServiceVolumeConfig {
|
||||
volumeName := strings.ToLower(filepath.VolumeName(volume.Source))
|
||||
if len(volumeName) != 2 {
|
||||
return volume
|
||||
}
|
||||
|
||||
convertedSource := fmt.Sprintf("/%c%s", volumeName[0], volume.Source[len(volumeName):])
|
||||
convertedSource = strings.ReplaceAll(convertedSource, "\\", "/")
|
||||
|
||||
volume.Source = convertedSource
|
||||
return volume
|
||||
}
|
||||
|
||||
func resolveEnvironment(serviceConfig *types.ServiceConfig, workingDir string, lookupEnv template.Mapping) error {
|
||||
environment := types.MappingWithEquals{}
|
||||
|
||||
|
@ -998,16 +1019,21 @@ var transformSSHConfig TransformerFunc = func(data interface{}) (interface{}, er
|
|||
}
|
||||
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 ParseShortSSHSyntax(value)
|
||||
}
|
||||
return nil, errors.Errorf("expected a sting, map or a list, got %T: %#v", data, data)
|
||||
}
|
||||
|
||||
// ParseShortSSHSyntax parse short syntax for SSH authentications
|
||||
func ParseShortSSHSyntax(value string) ([]types.SSHKey, error) {
|
||||
if value == "" {
|
||||
value = "default"
|
||||
}
|
||||
key, val := transformValueToMapEntry(value, "=", false)
|
||||
result := []types.SSHKey{{ID: key, Path: val.(string)}}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) {
|
||||
list := value.([]interface{})
|
||||
result := make([]string, len(list))
|
||||
|
|
|
@ -55,9 +55,11 @@ func checkConsistency(project *types.Project) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
for _, secret := range s.Secrets {
|
||||
if _, ok := project.Secrets[secret.Source]; !ok {
|
||||
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined secret %s", s.Name, secret.Source))
|
||||
if s.Build != nil {
|
||||
for _, secret := range s.Build.Secrets {
|
||||
if _, ok := project.Secrets[secret.Source]; !ok {
|
||||
return errors.Wrap(errdefs.ErrInvalid, fmt.Sprintf("service %q refers to undefined build secret %s", s.Name, secret.Source))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, config := range s.Configs {
|
||||
|
|
|
@ -101,7 +101,8 @@
|
|||
"target": {"type": "string"},
|
||||
"shm_size": {"type": ["integer", "string"]},
|
||||
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
|
||||
"isolation": {"type": "string"}
|
||||
"isolation": {"type": "string"},
|
||||
"secrets": {"$ref": "#/definitions/service_config_or_secret"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
|
@ -144,26 +145,7 @@
|
|||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
},
|
||||
"configs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"},
|
||||
"mode": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"configs": {"$ref": "#/definitions/service_config_or_secret"},
|
||||
"container_name": {"type": "string"},
|
||||
"cpu_count": {"type": "integer", "minimum": 0},
|
||||
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||
|
@ -352,26 +334,7 @@
|
|||
},
|
||||
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"shm_size": {"type": ["number", "string"]},
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"},
|
||||
"mode": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"secrets": {"$ref": "#/definitions/service_config_or_secret"},
|
||||
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
||||
"stdin_open": {"type": "boolean"},
|
||||
"stop_grace_period": {"type": "string", "format": "duration"},
|
||||
|
@ -809,6 +772,27 @@
|
|||
"additionalProperties": false
|
||||
},
|
||||
|
||||
"service_config_or_secret": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"type": "string"},
|
||||
"target": {"type": "string"},
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"},
|
||||
"mode": {"type": "number"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {"^x-": {}}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"constraints": {
|
||||
"service": {
|
||||
"id": "#/definitions/constraints/service",
|
||||
|
|
|
@ -246,6 +246,11 @@ func (p *Project) WithoutUnnecessaryResources() {
|
|||
for _, v := range s.Secrets {
|
||||
requiredSecrets[v.Source] = struct{}{}
|
||||
}
|
||||
if s.Build != nil {
|
||||
for _, v := range s.Build.Secrets {
|
||||
requiredSecrets[v.Source] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, v := range s.Configs {
|
||||
requiredConfigs[v.Source] = struct{}{}
|
||||
}
|
||||
|
|
|
@ -291,19 +291,20 @@ func (s set) toSlice() []string {
|
|||
|
||||
// BuildConfig is a type for build
|
||||
type BuildConfig struct {
|
||||
Context string `yaml:",omitempty" json:"context,omitempty"`
|
||||
Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
|
||||
Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
|
||||
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
|
||||
Labels Labels `yaml:",omitempty" json:"labels,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"`
|
||||
Isolation string `yaml:",omitempty" json:"isolation,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Target string `yaml:",omitempty" json:"target,omitempty"`
|
||||
Context string `yaml:",omitempty" json:"context,omitempty"`
|
||||
Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
|
||||
Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
|
||||
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
|
||||
Labels Labels `yaml:",omitempty" json:"labels,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"`
|
||||
Isolation string `yaml:",omitempty" json:"isolation,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Target string `yaml:",omitempty" json:"target,omitempty"`
|
||||
Secrets []ServiceSecretConfig `yaml:",omitempty" json:"secrets,omitempty"`
|
||||
|
||||
Extensions map[string]interface{} `yaml:",inline" json:"-"`
|
||||
}
|
||||
|
@ -678,6 +679,25 @@ type ServiceVolumeConfig struct {
|
|||
Extensions map[string]interface{} `yaml:",inline" json:"-"`
|
||||
}
|
||||
|
||||
// String render ServiceVolumeConfig as a volume string, one can parse back using loader.ParseVolume
|
||||
func (s ServiceVolumeConfig) String() string {
|
||||
access := "rw"
|
||||
if s.ReadOnly {
|
||||
access = "ro"
|
||||
}
|
||||
options := []string{access}
|
||||
if s.Bind != nil && s.Bind.SELinux != "" {
|
||||
options = append(options, s.Bind.SELinux)
|
||||
}
|
||||
if s.Bind != nil && s.Bind.Propagation != "" {
|
||||
options = append(options, s.Bind.Propagation)
|
||||
}
|
||||
if s.Volume != nil && s.Volume.NoCopy {
|
||||
options = append(options, "nocopy")
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", s.Source, s.Target, strings.Join(options, ","))
|
||||
}
|
||||
|
||||
const (
|
||||
// VolumeTypeBind is the type for mounting host dir
|
||||
VolumeTypeBind = "bind"
|
||||
|
|
|
@ -32,7 +32,7 @@ github.com/cenkalti/backoff/v4
|
|||
github.com/cespare/xxhash/v2
|
||||
# github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e
|
||||
## explicit
|
||||
# github.com/compose-spec/compose-go v1.2.1
|
||||
# github.com/compose-spec/compose-go v1.2.4
|
||||
## explicit
|
||||
github.com/compose-spec/compose-go/consts
|
||||
github.com/compose-spec/compose-go/dotenv
|
||||
|
|
Loading…
Reference in New Issue