Improve default handling for "service create"#32284
Conversation
06a40b3 to
55a0368
Compare
|
ping @dnephin |
dnephin
left a comment
There was a problem hiding this comment.
It's unfortunate that the pflag library tries to stay so close to the golang flag package API. It is not well suited for this type of behaviour.
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
I believe this is always passed the same value defaults.Service.Update.
Does this need to be a parameter? Could it look like the other defaults() functions and accept no params?
Also, this doesn't make any use of opts, which means it doesn't need to be an instance method. It should be just a function.
There was a problem hiding this comment.
This is passed either defaults.Service.Update or defaults.Rollback.Update. I agree that it's strange as a method on updateOpts. I'll change it to a function.
Any preference on whether it should be a single function that takes a *api.UpdateConfig (like this method), or separate functions that call a shared implementation with either the Update or Rollback default values?
There was a problem hiding this comment.
Ah, I missed that, thanks!
This method moved to a function as-is sgtm.
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
This doesn't use r, so shouldn't be a method on restartPolicyOptions()
cli/command/service/update.go
Outdated
There was a problem hiding this comment.
With my suggested changes above this function will no longer need a serviceOpts, since these methods become functions.
client/service_inspect.go
Outdated
There was a problem hiding this comment.
I believe the pattern we normally use here is opts ServiceInspectOptions.
It's true that right now this would be a struct with a single field, but the next time we have to go adding a new flag we'll only need to change two lines, instead of every place ServiceInspectWithRaw() is called (for the second time).
Boolean paramaters are also hard to understand from the calling function, having insertDefaults: true is much nicer to read.
There was a problem hiding this comment.
SGTM. I noticed that there was a mix of styles, with some functions taking verbose, or force boolean arguments. I can switch to an options structure if that's the preferred style.
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
This function became a lot harder to read. There's a lot of branching, and string formatting that's not really relevant to the primary responsibility of this function (adding flags).
Instead of a boolean argument, could we pass in a defaults map[string]string ?
All of this logic for formatting defaults can be moved to a buildServiceDefaultFlagMapping(), and passed as the argument from newCreateCommand(). In update we can pass an empty map[string]string{}.
With this new mapping all of the descriptions can become "shared text" + defaults[flagName].
I guess this is a little more complicated in the case where we are actually setting default values, not just error messages. I think that can be handled as well by creating a FlagDefaults struct (instead of just a map[string]string), and adding getInt64(string flagName) int64 accessors, along with another map for default values.
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
This formatting is duplicated quite a few times:
func formatDefault(value interface{}) string {
...
}
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
Why does the help string claim a default that isn't actually set as the default on the flag?
There was a problem hiding this comment.
DurationVar is a little weird. IIRC it shows the default value in the CLI help, but only if it's nonzero. I'd like us to always show the default on service create, even if it's zero, and never show it on service update. So it made sense to manipulate the flag description instead of providing a default value. If we did both, the default value would appear twice when the default is nonzero.
I can add a comment explaing this.
cli/command/service/opts.go
Outdated
There was a problem hiding this comment.
I would love to find a way to avoid needing flags here, but I can't find any right now.
Can we move this these back to the ToRestartPolicy() function, and add a flags arg ? (and the same with the few below this)
bf92ba3 to
6064953
Compare
|
@dnephin: Thanks for the thoughtful review. I've addressed the comments - PTAL |
6064953 to
1c5fb33
Compare
|
Rebased |
|
I like this, finally time to give it a quick look (from a UX perspective), and seeing actual defaults in I haven't had time yet to test against an actually older daemon, but running this; Seems to still show the defaults in |
Yes, it's expected. I've pondered the idea of adding an API endpoint where the client can query what defaults are in place on the manager. But it seems like a lot of complexity for something that is generally not a factor (defaults change rarely, and mixed client/server version setups are unusual, and the worst case outcome is outdated help text). In this implementation, the defaults shown in CLI help are linked into the client at build time. If you think fetching the defaults via an API is worth it, we could consider going in that direction. |
1c5fb33 to
9d94390
Compare
|
Rebased |
9d94390 to
17e9e60
Compare
|
Rebased |
17e9e60 to
1d6d4d7
Compare
|
design lgtm. |
vdemeester
left a comment
There was a problem hiding this comment.
Design LGTM, moving to code review
|
@aaronlehmann needs a rebase 👼 |
1d6d4d7 to
bf47928
Compare
|
Rebased. |
|
@aaronlehmann seems like the windows failure is related |
|
Deleted my earlier comment, because it was indeed a problem with this PR. I thought it was an existing problem on master, but it turned out the reason it only showed up on windows was because each builder does a separate merge to master so they're not building the same version of the code (which is kind of a problem IMHO!). |
bf47928 to
d9331a2
Compare
|
@vieux: Fixed, thanks! |
|
I'm a bit confused, but probably do something wrong.. docker service create --restart-condition="on-failure" --name web nginx:alpine
2i3pres421je9kuo5rh1vf2n4Makes this request; {
"EndpointSpec": {
"Mode": "vip"
},
"Labels": {},
"Mode": {
"Replicated": {}
},
"Name": "web",
"TaskTemplate": {
"ContainerSpec": {
"DNSConfig": {},
"Image": "nginx:alpine"
},
"ForceUpdate": 0,
"Placement": {},
"Resources": {
"Limits": {},
"Reservations": {}
},
"RestartPolicy": {
"Condition": "on-failure",
"Delay": 5000000000,
"MaxAttempts": 0
}
}
}With the / # curl --unix-socket /var/run/docker.sock -sL http://localhost/v1.29/services/web?insertDefaults=true | jq .
{
"ID": "2i3pres421je9kuo5rh1vf2n4",
"Version": {
"Index": 31
},
"CreatedAt": "2017-04-10T09:36:58.859857088Z",
"UpdatedAt": "2017-04-10T09:36:58.859857088Z",
"Spec": {
"Name": "web",
"Labels": {},
"TaskTemplate": {
"ContainerSpec": {
"Image": "nginx:alpine@sha256:5aadb68304a38a8e2719605e4e180413f390cd6647602bee9bdedd59753c3590",
"DNSConfig": {}
},
"Resources": {
"Limits": {},
"Reservations": {}
},
"RestartPolicy": {
"Condition": "on-failure",
"Delay": 5000000000,
"MaxAttempts": 0
},
"Placement": {},
"ForceUpdate": 0
},
"Mode": {
"Replicated": {
"Replicas": 1
}
},
"EndpointSpec": {
"Mode": "vip"
}
},
"Endpoint": {
"Spec": {}
}
}Without the / # curl --unix-socket /var/run/docker.sock -sL http://localhost/v1.29/services/web | jq .
{
"ID": "2i3pres421je9kuo5rh1vf2n4",
"Version": {
"Index": 31
},
"CreatedAt": "2017-04-10T09:36:58.859857088Z",
"UpdatedAt": "2017-04-10T09:36:58.859857088Z",
"Spec": {
"Name": "web",
"Labels": {},
"TaskTemplate": {
"ContainerSpec": {
"Image": "nginx:alpine@sha256:5aadb68304a38a8e2719605e4e180413f390cd6647602bee9bdedd59753c3590",
"DNSConfig": {}
},
"Resources": {
"Limits": {},
"Reservations": {}
},
"RestartPolicy": {
"Condition": "on-failure",
"Delay": 5000000000,
"MaxAttempts": 0
},
"Placement": {},
"ForceUpdate": 0
},
"Mode": {
"Replicated": {
"Replicas": 1
}
},
"EndpointSpec": {
"Mode": "vip"
}
},
"Endpoint": {
"Spec": {}
}
} |
d9331a2 to
c115d68
Compare
|
@thaJeztah: You're right - there was a problem. Specifying the service ID led to the correct behavior, but services names were not handled properly, as the service was returned directly from |
This adds a new parameter insertDefaults to /services/{id}. When this is
set, an empty field (such as UpdateConfig) will be populated with
default values in the API response. Make "service inspect" use this, so
that empty fields do not result in missing information when inspecting a
service.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
If no fields related to an update config or restart policy are specified, these structs should not be created as part of the service, to avoid hardcoding the current defaults. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
c115d68 to
bbe1202
Compare
|
Ah! That looks better; docker service inspect web
[
{
"ID": "k850seipz5ohpemq1cotenze2",
"Version": {
"Index": 10
},
"CreatedAt": "2017-04-11T10:51:21.181483381Z",
"UpdatedAt": "2017-04-11T10:51:21.181483381Z",
"Spec": {
"Name": "web",
"Labels": {},
"TaskTemplate": {
"ContainerSpec": {
"Image": "nginx:alpine@sha256:5aadb68304a38a8e2719605e4e180413f390cd6647602bee9bdedd59753c3590",
"StopGracePeriod": 10000000000,
"DNSConfig": {}
},
"Resources": {
"Limits": {},
"Reservations": {}
},
"RestartPolicy": {
"Condition": "on-failure",
"Delay": 5000000000,
"MaxAttempts": 0
},
"Placement": {},
"ForceUpdate": 0
},
"Mode": {
"Replicated": {
"Replicas": 1
}
},
"UpdateConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"RollbackConfig": {
"Parallelism": 1,
"FailureAction": "pause",
"Monitor": 5000000000,
"MaxFailureRatio": 0,
"Order": "stop-first"
},
"EndpointSpec": {
"Mode": "vip"
}
},
"Endpoint": {
"Spec": {}
}
}
] |
| description: "ID or name of service." | ||
| required: true | ||
| type: "string" | ||
| - name: "insertDefaults" |
There was a problem hiding this comment.
This needs to be added to the API version-history; https://github.com/docker/docker/blob/master/docs/api/version-history.md
(ok with doing that in a follow-up)
|
All green; I'll go ahead and merge so that this is in 17.05. I'll include the API version changes #32284 (comment) in the "changelog" pull request |
Improve default handling for "service create"
Improve default handling for "service create"
Currently, the CLI hardcodes several defaults. This is a problem because it's awkward to keep CLI defaults in sync with swarmkit defaults. Also, when a service is being created, it generally takes on several default values of flags that the user did not specify. If these defaults are changed later in swarmkit, they will not apply to those services, because they will continue to use whatever the CLI hardcoded at the time.
This PR makes two changes across two commits.
Changes
docker service inspectto use the newInsertDefaultsflag so that fields that are omitted in the service definition show default values indocker service inspect. This paves the way fordocker service createto avoid hardcoding its defaults in services without hiding information frominspect.Changes
docker service createto omit fields that are not specified by the user, when possible. This will allow defaults to be applied inside the manager. To provide visibility into defaults in the CLI help, the CLI uses swarmkit'sapi/defaultspackage to annotatedocker service create --helpoutput. These defaults are left out ofdocker service update --help, because defaults are misleading in this context - if a flag is not specified, it has no default action.Fixes #24959
Fixes #32094
ping @aluzzardi @dongluochen @dnephin @thaJeztah