-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for multiple composefile when deploying #569
Add support for multiple composefile when deploying #569
Conversation
2455685
to
7ff744d
Compare
cli/command/stack/deploy.go
Outdated
@@ -63,9 +63,9 @@ func runDeploy(dockerCli command.Cli, opts deployOptions) error { | |||
} | |||
|
|||
switch { | |||
case opts.bundlefile == "" && opts.composefile == "": | |||
case opts.bundlefile == "" && len(opts.composefile) == 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should keep the symmetry :)
i mean on checking the emptiness of a string always using len function instead of '== "" '
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really dislike checking empty string using len(string) == 0
. Sure it works, but it's a lot less obvious when reading the code. I think most people say "empty string", not "a string of zero bytes", so variable == ""
matches this phrase.
I'm in favour of keeping it as it is now.
cli/command/stack/deploy.go
Outdated
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).") | ||
case opts.bundlefile != "" && opts.composefile != "": | ||
case opts.bundlefile != "" && len(opts.composefile) != 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same thing :)
_, err := Load(configDetails) | ||
require.Error(t, err) | ||
} | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think we should remove commented code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vdemeester all right :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh but it doesn't really make sense in the PR because it's not the one implementing parsing of v1/v2 compose files 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not supported, it's an error to combine different versions.
"networks": map[string]interface{}{}, | ||
"secrets": map[string]interface{}{}, | ||
"configs": map[string]interface{}{}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmmm can we extract something ? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to write a builder for that once it's in code-review
yes 😉
"networks": map[string]interface{}{}, | ||
"secrets": map[string]interface{}{}, | ||
"configs": map[string]interface{}{}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here i think we can extract it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
cli/command/stack/deploy.go
Outdated
@@ -22,7 +22,7 @@ const ( | |||
|
|||
type deployOptions struct { | |||
bundlefile string | |||
composefile string | |||
composefile []string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to composefiles ?
cli/compose/loader/loader.go
Outdated
|
||
if services, ok := configDict["services"]; ok { | ||
if servicesDict, ok := services.(map[string]interface{}); ok { | ||
forbidden := getProperties(servicesDict, types.ForbiddenProperties) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if forbidden := getProperties(servicesDict, types.ForbiddenProperties); len(forbidden) > 0{
return nil, &ForbiddenPropertiesError{Properties: forbidden}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 yes, I think that'd be cleaner
cli/compose/loader/loader.go
Outdated
return nil, err | ||
} | ||
|
||
cfg.Configs, err = LoadConfigObjs(config["configs"], configDetails.WorkingDir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you check err here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oups, I forgot 😓
} | ||
|
||
func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error { | ||
if fn, ok := s.m[t]; ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you just return s.m[t]?
func (s *specials) Transformer(t reflect.Type) func(dst, src reflect.Value) error {
return s.m[t]
}
Isn't it equivalent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be 👍
} | ||
|
||
func merge(configs []*types.Config) (*types.Config, error) { | ||
base := configs[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check configs length before access to the first element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, it's guaranteed that you have a least one config (and as it's not exported.. I think we can "not check" but open to change that)
7ff744d
to
03c9cdd
Compare
Codecov Report
@@ Coverage Diff @@
## master #569 +/- ##
==========================================
- Coverage 52.95% 51.95% -1.01%
==========================================
Files 244 245 +1
Lines 15839 15996 +157
==========================================
- Hits 8387 8310 -77
- Misses 6898 7125 +227
- Partials 554 561 +7 |
Hmm, jenkins build didn't trigger (or didn't update the github PR) for some reason. I've seen that happen occasionally.
I don't think we should read this by default. This is similar to the
Haven't looked at existing tests yet, but something that we need to make sure is tested is the case where we don't actually merge structs but have one struct override the other (ex: build). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Design LGTM
Finding all the necessary test cases will be difficult. There are a ton in docker-compose. If we can find all the places that don't fully merge structs that would be a good thing to test.
Design LGTM as well; want to give this a bit of a try; ideally we'd also have a way to print the merged stack before deploying (similar to |
03c9cdd
to
6869e3c
Compare
4809114
to
a34dd26
Compare
cli/compose/loader/loader.go
Outdated
} | ||
for _, file := range configDetails.ConfigFiles { | ||
configDict := file.Config | ||
configDetails.Version = schema.Version(configDict) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should only set the version from the first config, and error if any other config does not match that version.
cli/compose/loader/merge.go
Outdated
for name, overrideService := range overrideServices { | ||
if baseService, ok := baseServices[name]; ok { | ||
if err := mergo.Merge(&baseService, &overrideService, mergo.WithOverride, mergo.WithTransformers(specials)); err != nil { | ||
// if err := mergo.MergeWithOverwriteAndTransform(&baseService, &overrideService, specials); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commented code
cli/compose/loader/merge.go
Outdated
// merge services | ||
base.Services, err = mergeServices(base.Services, override.Services) | ||
if err != nil { | ||
return base, errors.Wrap(err, "cannot merge services") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors here might be different to debug when there are many files because there is no mention of which file failed to merge. Maybe types.Config
needs a Filename
field so that it can be included here?
cli/compose/loader/merge.go
Outdated
if baseService, ok := baseServices[name]; ok { | ||
if err := mergo.Merge(&baseService, &overrideService, mergo.WithOverride, mergo.WithTransformers(specials)); err != nil { | ||
// if err := mergo.MergeWithOverwriteAndTransform(&baseService, &overrideService, specials); err != nil { | ||
return base, errors.Wrap(err, "cannot merge compose file") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message seems more appropriate as a wrap around the error returned by merge()
. At this level I think the detail that needs to be included is the service name.
cli/compose/loader/merge.go
Outdated
overrideServices := mapByName(override) | ||
specials := &specials{ | ||
m: map[reflect.Type]func(dst, src reflect.Value) error{ | ||
reflect.TypeOf(types.BuildConfig{}): func(dst, src reflect.Value) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that build was a non-recursive merge, but looking at the docker-compose code again it seems like maybe it does recursive?
logging
looks like a special case, where it changes if the drier changes. I think driver options are a case where they don't get merged but overridden. I don't have a full list but I think I remember there being a bunch of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hum 🤔 what should we do here ? 😛
logging
is a special case but build isn't ?
cc @shin-
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding logging
, the idea is that options are driver-specific, thus an override of the driver
resets the options
dictionary. Unless the base driver is null or unset, in which case it's assumed the user is actively attempting to use inheritance, and base options are merged with the override. Same thing if the base has specified a driver but not the override.
These tests codify the behavior on the docker-compose side: https://github.com/docker/compose/blob/master/tests/unit/config/config_test.py#L1929-L2127
I'm not sure what the question is about build
?
Sorry, I didn't see this before. I'll check it by this weekend. |
a34dd26
to
bcc50a6
Compare
Rebase and updated.. I need to take your comments into account @dnephin 😛 |
Some testing (will post more); file1.yml: version: "3.5"
services:
web:
image: nginx:alpine
ports:
- "8080:80" file2.yml: version: "3.5"
services:
web:
image: nginx:alpine
ports:
- target: 80
published: 8080
Should the client here detect the duplicate definition? This is what's sent by the CLI: "EndpointSpec": {
"Ports": [
{
"PublishedPort": 8080,
"TargetPort": 80
},
{
"PublishedPort": 8080,
"TargetPort": 80
}
]
}, |
c76178c
to
8a7db05
Compare
@dnephin @thaJeztah should be ready for another round of review 👼 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
cli/compose/loader/merge.go
Outdated
func sliceToMap(tomap tomapFn, v reflect.Value) (map[interface{}]interface{}, error) { | ||
// check if valid | ||
if !v.IsValid() { | ||
return nil, errors.Errorf("invalid..") // FIXME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FIXME
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂️
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
8a7db05
to
1872bd8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🎉
As a follow up, we'll need;
|
@thaJeztah so what exactly is the CLI syntax? Just Thanks! Nevermind, just saw the milestone! I don't have it yet. |
Thanks for implementing this, so glad this finally got to swarm. I have an edge case that keeps throwign an error. The two files are: file A:
file B:
If file A is overwritten by file B, everything is fine, but if file B gets overwritten by file A, the following error gets thrown:
|
Using a custom version of mergo to manage special cases.
Handlingdocker-compose.override.yml
per defaultdocker-compose
Should fix moby/moby#30127
/cc @shin- too for edge case in the merge that I would have missed
as of now, it is supposed to be red (i.e. vendor should not validate)
Signed-off-by: Vincent Demeester vincent@sbr.pm