Add support for multiple composefile when deploying#569
Conversation
2455685 to
7ff744d
Compare
|
|
||
| switch { | ||
| case opts.bundlefile == "" && opts.composefile == "": | ||
| case opts.bundlefile == "" && len(opts.composefile) == 0: |
There was a problem hiding this comment.
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.
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.
| case opts.bundlefile == "" && len(opts.composefile) == 0: | ||
| 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: |
| _, err := Load(configDetails) | ||
| require.Error(t, err) | ||
| } | ||
| */ |
There was a problem hiding this comment.
i think we should remove commented code :)
There was a problem hiding this comment.
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.
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.
mmmm can we extract something ? :)
There was a problem hiding this comment.
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.
same here i think we can extract it
| type deployOptions struct { | ||
| bundlefile string | ||
| composefile string | ||
| composefile []string |
There was a problem hiding this comment.
Rename to composefiles ?
|
|
||
| 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.
if forbidden := getProperties(servicesDict, types.ForbiddenProperties); len(forbidden) > 0{
return nil, &ForbiddenPropertiesError{Properties: forbidden}
}There was a problem hiding this comment.
👍 yes, I think that'd be cleaner
| return nil, err | ||
| } | ||
|
|
||
| cfg.Configs, err = LoadConfigObjs(config["configs"], configDetails.WorkingDir) |
There was a problem hiding this comment.
Why don't you check err here?
There was a problem hiding this comment.
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.
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?
| } | ||
|
|
||
| func merge(configs []*types.Config) (*types.Config, error) { | ||
| base := configs[0] |
There was a problem hiding this comment.
Check configs length before access to the first element?
There was a problem hiding this comment.
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). |
dnephin
left a comment
There was a problem hiding this comment.
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
| } | ||
| for _, file := range configDetails.ConfigFiles { | ||
| configDict := file.Config | ||
| configDetails.Version = schema.Version(configDict) |
There was a problem hiding this comment.
I think this should only set the version from the first config, and error if any other config does not match that version.
| 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 { |
| // 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.
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?
| 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.
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.
| 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.
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.
hum 🤔 what should we do here ? 😛
logging is a special case but build isn't ?
cc @shin-
There was a problem hiding this comment.
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: 8080Should 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 👼 |
| func sliceToMap(tomap tomapFn, v reflect.Value) (map[interface{}]interface{}, error) { | ||
| // check if valid | ||
| if !v.IsValid() { | ||
| return nil, errors.Errorf("invalid..") // FIXME |
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
8a7db05 to
1872bd8
Compare
|
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: |
…n-mergo Add support for multiple composefile when deploying Upstream-commit: 25e969c Component: cli
Using a custom version of mergo to manage special cases.
Handlingdocker-compose.override.ymlper defaultdocker-composeShould 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