2016-09-08 13:11:39 -04:00
package service
import (
2018-05-03 21:02:44 -04:00
"context"
2016-09-08 13:11:39 -04:00
"fmt"
2017-03-23 20:51:57 -04:00
"sort"
2016-09-08 13:11:39 -04:00
"strconv"
"strings"
"time"
2017-05-15 08:45:19 -04:00
"github.com/docker/cli/opts"
2017-06-12 22:53:53 -04:00
"github.com/docker/docker/api/types"
2016-10-13 14:28:32 -04:00
"github.com/docker/docker/api/types/container"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types/swarm"
2018-10-10 06:18:29 -04:00
"github.com/docker/docker/api/types/versions"
2017-05-08 13:51:30 -04:00
"github.com/docker/docker/client"
2017-03-30 21:35:04 -04:00
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/defaults"
gogotypes "github.com/gogo/protobuf/types"
2019-04-13 11:51:15 -04:00
"github.com/google/shlex"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2017-03-15 16:49:52 -04:00
"github.com/spf13/pflag"
2016-09-08 13:11:39 -04:00
)
type int64Value interface {
Value ( ) int64
}
// Uint64Opt represents a uint64.
type Uint64Opt struct {
value * uint64
}
// Set a new value on the option
func ( i * Uint64Opt ) Set ( s string ) error {
v , err := strconv . ParseUint ( s , 0 , 64 )
i . value = & v
return err
}
2016-11-08 10:06:07 -05:00
// Type returns the type of this option, which will be displayed in `--help` output
2016-09-08 13:11:39 -04:00
func ( i * Uint64Opt ) Type ( ) string {
2016-11-08 10:06:07 -05:00
return "uint"
2016-09-08 13:11:39 -04:00
}
// String returns a string repr of this option
func ( i * Uint64Opt ) String ( ) string {
if i . value != nil {
return fmt . Sprintf ( "%v" , * i . value )
}
2016-12-15 09:12:33 -05:00
return ""
2016-09-08 13:11:39 -04:00
}
// Value returns the uint64
func ( i * Uint64Opt ) Value ( ) * uint64 {
return i . value
}
2016-11-08 10:06:07 -05:00
type floatValue float32
func ( f * floatValue ) Set ( s string ) error {
v , err := strconv . ParseFloat ( s , 32 )
* f = floatValue ( v )
return err
}
func ( f * floatValue ) Type ( ) string {
return "float"
}
func ( f * floatValue ) String ( ) string {
return strconv . FormatFloat ( float64 ( * f ) , 'g' , - 1 , 32 )
}
func ( f * floatValue ) Value ( ) float32 {
return float32 ( * f )
}
2017-01-19 18:27:37 -05:00
// placementPrefOpts holds a list of placement preferences.
type placementPrefOpts struct {
prefs [ ] swarm . PlacementPreference
strings [ ] string
}
func ( opts * placementPrefOpts ) String ( ) string {
if len ( opts . strings ) == 0 {
return ""
}
return fmt . Sprintf ( "%v" , opts . strings )
}
// Set validates the input value and adds it to the internal slices.
// Note: in the future strategies other than "spread", may be supported,
// as well as additional comma-separated options.
func ( opts * placementPrefOpts ) Set ( value string ) error {
fields := strings . Split ( value , "=" )
if len ( fields ) != 2 {
return errors . New ( ` placement preference must be of the format "<strategy>=<arg>" ` )
}
if fields [ 0 ] != "spread" {
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "unsupported placement preference %s (only spread is supported)" , fields [ 0 ] )
2017-01-19 18:27:37 -05:00
}
opts . prefs = append ( opts . prefs , swarm . PlacementPreference {
Spread : & swarm . SpreadOver {
SpreadDescriptor : fields [ 1 ] ,
} ,
} )
opts . strings = append ( opts . strings , value )
return nil
}
// Type returns a string name for this Option type
func ( opts * placementPrefOpts ) Type ( ) string {
return "pref"
}
2016-12-07 14:37:55 -05:00
// ShlexOpt is a flag Value which parses a string as a list of shell words
type ShlexOpt [ ] string
// Set the value
func ( s * ShlexOpt ) Set ( value string ) error {
valueSlice , err := shlex . Split ( value )
* s = ShlexOpt ( valueSlice )
return err
}
// Type returns the tyep of the value
func ( s * ShlexOpt ) Type ( ) string {
return "command"
}
func ( s * ShlexOpt ) String ( ) string {
2017-03-30 21:35:04 -04:00
if len ( * s ) == 0 {
return ""
}
2016-12-07 14:37:55 -05:00
return fmt . Sprint ( * s )
}
// Value returns the value as a string slice
func ( s * ShlexOpt ) Value ( ) [ ] string {
return [ ] string ( * s )
}
2016-09-08 13:11:39 -04:00
type updateOptions struct {
2016-09-02 17:12:05 -04:00
parallelism uint64
delay time . Duration
monitor time . Duration
onFailure string
2016-11-08 10:06:07 -05:00
maxFailureRatio floatValue
2017-01-18 14:38:19 -05:00
order string
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
func updateConfigFromDefaults ( defaultUpdateConfig * api . UpdateConfig ) * swarm . UpdateConfig {
defaultFailureAction := strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaultUpdateConfig . FailureAction ) ] )
defaultMonitor , _ := gogotypes . DurationFromProto ( defaultUpdateConfig . Monitor )
2017-02-15 19:04:30 -05:00
return & swarm . UpdateConfig {
2017-03-30 21:35:04 -04:00
Parallelism : defaultUpdateConfig . Parallelism ,
Delay : defaultUpdateConfig . Delay ,
Monitor : defaultMonitor ,
FailureAction : defaultFailureAction ,
MaxFailureRatio : defaultUpdateConfig . MaxFailureRatio ,
Order : defaultOrder ( defaultUpdateConfig . Order ) ,
}
}
func ( opts updateOptions ) updateConfig ( flags * pflag . FlagSet ) * swarm . UpdateConfig {
if ! anyChanged ( flags , flagUpdateParallelism , flagUpdateDelay , flagUpdateMonitor , flagUpdateFailureAction , flagUpdateMaxFailureRatio ) {
return nil
}
updateConfig := updateConfigFromDefaults ( defaults . Service . Update )
if flags . Changed ( flagUpdateParallelism ) {
updateConfig . Parallelism = opts . parallelism
}
if flags . Changed ( flagUpdateDelay ) {
updateConfig . Delay = opts . delay
}
if flags . Changed ( flagUpdateMonitor ) {
updateConfig . Monitor = opts . monitor
}
if flags . Changed ( flagUpdateFailureAction ) {
updateConfig . FailureAction = opts . onFailure
}
if flags . Changed ( flagUpdateMaxFailureRatio ) {
updateConfig . MaxFailureRatio = opts . maxFailureRatio . Value ( )
}
if flags . Changed ( flagUpdateOrder ) {
updateConfig . Order = opts . order
}
return updateConfig
}
func ( opts updateOptions ) rollbackConfig ( flags * pflag . FlagSet ) * swarm . UpdateConfig {
if ! anyChanged ( flags , flagRollbackParallelism , flagRollbackDelay , flagRollbackMonitor , flagRollbackFailureAction , flagRollbackMaxFailureRatio ) {
return nil
}
updateConfig := updateConfigFromDefaults ( defaults . Service . Rollback )
if flags . Changed ( flagRollbackParallelism ) {
updateConfig . Parallelism = opts . parallelism
}
if flags . Changed ( flagRollbackDelay ) {
updateConfig . Delay = opts . delay
}
if flags . Changed ( flagRollbackMonitor ) {
updateConfig . Monitor = opts . monitor
}
if flags . Changed ( flagRollbackFailureAction ) {
updateConfig . FailureAction = opts . onFailure
2017-02-15 19:04:30 -05:00
}
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagRollbackMaxFailureRatio ) {
updateConfig . MaxFailureRatio = opts . maxFailureRatio . Value ( )
}
if flags . Changed ( flagRollbackOrder ) {
updateConfig . Order = opts . order
}
return updateConfig
2017-02-15 19:04:30 -05:00
}
2016-09-08 13:11:39 -04:00
type resourceOptions struct {
2017-11-08 11:33:36 -05:00
limitCPU opts . NanoCPUs
limitMemBytes opts . MemBytes
2020-04-29 11:11:48 -04:00
limitPids int64
2017-11-08 11:33:36 -05:00
resCPU opts . NanoCPUs
resMemBytes opts . MemBytes
resGenericResources [ ] string
2016-09-08 13:11:39 -04:00
}
2017-11-08 11:33:36 -05:00
func ( r * resourceOptions ) ToResourceRequirements ( ) ( * swarm . ResourceRequirements , error ) {
generic , err := ParseGenericResources ( r . resGenericResources )
if err != nil {
return nil , err
}
2016-09-08 13:11:39 -04:00
return & swarm . ResourceRequirements {
2020-06-15 05:26:48 -04:00
Limits : & swarm . Limit {
2016-09-08 13:11:39 -04:00
NanoCPUs : r . limitCPU . Value ( ) ,
MemoryBytes : r . limitMemBytes . Value ( ) ,
2020-04-29 11:11:48 -04:00
Pids : r . limitPids ,
2016-09-08 13:11:39 -04:00
} ,
Reservations : & swarm . Resources {
2017-11-08 11:33:36 -05:00
NanoCPUs : r . resCPU . Value ( ) ,
MemoryBytes : r . resMemBytes . Value ( ) ,
GenericResources : generic ,
2016-09-08 13:11:39 -04:00
} ,
2017-11-08 11:33:36 -05:00
} , nil
2016-09-08 13:11:39 -04:00
}
type restartPolicyOptions struct {
condition string
2017-05-16 11:49:40 -04:00
delay opts . DurationOpt
2016-09-08 13:11:39 -04:00
maxAttempts Uint64Opt
2017-05-16 11:49:40 -04:00
window opts . DurationOpt
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
func defaultRestartPolicy ( ) * swarm . RestartPolicy {
defaultMaxAttempts := defaults . Service . Task . Restart . MaxAttempts
rp := & swarm . RestartPolicy {
MaxAttempts : & defaultMaxAttempts ,
}
if defaults . Service . Task . Restart . Delay != nil {
defaultRestartDelay , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Delay )
rp . Delay = & defaultRestartDelay
}
if defaults . Service . Task . Restart . Window != nil {
defaultRestartWindow , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Window )
rp . Window = & defaultRestartWindow
}
rp . Condition = defaultRestartCondition ( )
return rp
}
func defaultRestartCondition ( ) swarm . RestartPolicyCondition {
switch defaults . Service . Task . Restart . Condition {
case api . RestartOnNone :
return "none"
case api . RestartOnFailure :
return "on-failure"
case api . RestartOnAny :
return "any"
default :
return ""
}
}
func defaultOrder ( order api . UpdateConfig_UpdateOrder ) string {
switch order {
case api . UpdateConfig_STOP_FIRST :
return "stop-first"
case api . UpdateConfig_START_FIRST :
return "start-first"
default :
return ""
}
}
func ( r * restartPolicyOptions ) ToRestartPolicy ( flags * pflag . FlagSet ) * swarm . RestartPolicy {
if ! anyChanged ( flags , flagRestartDelay , flagRestartMaxAttempts , flagRestartWindow , flagRestartCondition ) {
return nil
}
restartPolicy := defaultRestartPolicy ( )
if flags . Changed ( flagRestartDelay ) {
restartPolicy . Delay = r . delay . Value ( )
}
if flags . Changed ( flagRestartCondition ) {
restartPolicy . Condition = swarm . RestartPolicyCondition ( r . condition )
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagRestartMaxAttempts ) {
restartPolicy . MaxAttempts = r . maxAttempts . Value ( )
}
if flags . Changed ( flagRestartWindow ) {
restartPolicy . Window = r . window . Value ( )
}
return restartPolicy
2016-09-08 13:11:39 -04:00
}
2017-03-29 18:55:21 -04:00
type credentialSpecOpt struct {
value * swarm . CredentialSpec
source string
}
func ( c * credentialSpecOpt ) Set ( value string ) error {
c . source = value
c . value = & swarm . CredentialSpec { }
switch {
2019-02-02 10:35:26 -05:00
case strings . HasPrefix ( value , "config://" ) :
2019-03-27 16:44:32 -04:00
// NOTE(dperny): we allow the user to specify the value of
// CredentialSpec Config using the Name of the config, but the API
// requires the ID of the config. For simplicity, we will parse
// whatever value is provided into the "Config" field, but before
// making API calls, we may need to swap the Config Name for the ID.
// Therefore, this isn't the definitive location for the value of
// Config that is passed to the API.
2019-02-02 10:35:26 -05:00
c . value . Config = strings . TrimPrefix ( value , "config://" )
2017-03-29 18:55:21 -04:00
case strings . HasPrefix ( value , "file://" ) :
c . value . File = strings . TrimPrefix ( value , "file://" )
case strings . HasPrefix ( value , "registry://" ) :
c . value . Registry = strings . TrimPrefix ( value , "registry://" )
2019-03-27 16:44:32 -04:00
case value == "" :
// if the value of the flag is an empty string, that means there is no
// CredentialSpec needed. This is useful for removing a CredentialSpec
// during a service update.
2017-03-29 18:55:21 -04:00
default :
2019-02-02 10:35:26 -05:00
return errors . New ( ` invalid credential spec: value must be prefixed with "config://", "file://", or "registry://" ` )
2017-03-29 18:55:21 -04:00
}
return nil
}
func ( c * credentialSpecOpt ) Type ( ) string {
return "credential-spec"
}
func ( c * credentialSpecOpt ) String ( ) string {
return c . source
}
func ( c * credentialSpecOpt ) Value ( ) * swarm . CredentialSpec {
return c . value
}
2018-01-03 09:40:55 -05:00
func resolveNetworkID ( ctx context . Context , apiClient client . NetworkAPIClient , networkIDOrName string ) ( string , error ) {
nw , err := apiClient . NetworkInspect ( ctx , networkIDOrName , types . NetworkInspectOptions { Scope : "swarm" } )
return nw . ID , err
}
func convertNetworks ( networks opts . NetworkOpt ) [ ] swarm . NetworkAttachmentConfig {
2017-05-09 19:29:04 -04:00
var netAttach [ ] swarm . NetworkAttachmentConfig
for _ , net := range networks . Value ( ) {
2017-10-11 12:18:27 -04:00
netAttach = append ( netAttach , swarm . NetworkAttachmentConfig {
2017-06-21 00:11:59 -04:00
Target : net . Target ,
Aliases : net . Aliases ,
DriverOpts : net . DriverOpts ,
} )
2016-09-08 13:11:39 -04:00
}
2018-01-03 09:40:55 -05:00
return netAttach
2016-09-08 13:11:39 -04:00
}
type endpointOptions struct {
2016-12-08 16:32:10 -05:00
mode string
publishPorts opts . PortOpt
2016-09-08 13:11:39 -04:00
}
func ( e * endpointOptions ) ToEndpointSpec ( ) * swarm . EndpointSpec {
return & swarm . EndpointSpec {
Mode : swarm . ResolutionMode ( strings . ToLower ( e . mode ) ) ,
2016-12-08 16:32:10 -05:00
Ports : e . publishPorts . Value ( ) ,
2016-09-08 13:11:39 -04:00
}
}
type logDriverOptions struct {
name string
opts opts . ListOpts
}
func newLogDriverOptions ( ) logDriverOptions {
2016-12-23 14:09:12 -05:00
return logDriverOptions { opts : opts . NewListOpts ( opts . ValidateEnv ) }
2016-09-08 13:11:39 -04:00
}
func ( ldo * logDriverOptions ) toLogDriver ( ) * swarm . Driver {
if ldo . name == "" {
return nil
}
// set the log driver only if specified.
return & swarm . Driver {
Name : ldo . name ,
2017-06-05 18:23:21 -04:00
Options : opts . ConvertKVStringsToMap ( ldo . opts . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
}
}
2016-10-13 14:28:32 -04:00
type healthCheckOptions struct {
cmd string
2017-05-16 11:49:40 -04:00
interval opts . PositiveDurationOpt
timeout opts . PositiveDurationOpt
2016-10-13 14:28:32 -04:00
retries int
2017-05-16 11:49:40 -04:00
startPeriod opts . PositiveDurationOpt
2016-10-13 14:28:32 -04:00
noHealthcheck bool
}
func ( opts * healthCheckOptions ) toHealthConfig ( ) ( * container . HealthConfig , error ) {
var healthConfig * container . HealthConfig
haveHealthSettings := opts . cmd != "" ||
opts . interval . Value ( ) != nil ||
opts . timeout . Value ( ) != nil ||
opts . retries != 0
if opts . noHealthcheck {
if haveHealthSettings {
2017-03-09 13:23:45 -05:00
return nil , errors . Errorf ( "--%s conflicts with --health-* options" , flagNoHealthcheck )
2016-10-13 14:28:32 -04:00
}
healthConfig = & container . HealthConfig { Test : [ ] string { "NONE" } }
} else if haveHealthSettings {
var test [ ] string
if opts . cmd != "" {
test = [ ] string { "CMD-SHELL" , opts . cmd }
}
2016-11-29 04:58:47 -05:00
var interval , timeout , startPeriod time . Duration
2016-10-13 14:28:32 -04:00
if ptr := opts . interval . Value ( ) ; ptr != nil {
interval = * ptr
}
if ptr := opts . timeout . Value ( ) ; ptr != nil {
timeout = * ptr
}
2016-11-29 04:58:47 -05:00
if ptr := opts . startPeriod . Value ( ) ; ptr != nil {
startPeriod = * ptr
}
2016-10-13 14:28:32 -04:00
healthConfig = & container . HealthConfig {
2016-11-29 04:58:47 -05:00
Test : test ,
Interval : interval ,
Timeout : timeout ,
Retries : opts . retries ,
StartPeriod : startPeriod ,
2016-10-13 14:28:32 -04:00
}
}
return healthConfig , nil
}
2016-11-03 11:05:00 -04:00
// convertExtraHostsToSwarmHosts converts an array of extra hosts in cli
// <host>:<ip>
// into a swarmkit host format:
// IP_address canonical_hostname [aliases...]
// This assumes input value (<host>:<ip>) has already been validated
func convertExtraHostsToSwarmHosts ( extraHosts [ ] string ) [ ] string {
hosts := [ ] string { }
for _ , extraHost := range extraHosts {
parts := strings . SplitN ( extraHost , ":" , 2 )
hosts = append ( hosts , fmt . Sprintf ( "%s %s" , parts [ 1 ] , parts [ 0 ] ) )
}
return hosts
}
2016-09-08 13:11:39 -04:00
type serviceOptions struct {
2017-02-16 20:05:36 -05:00
detach bool
quiet bool
2016-09-08 13:11:39 -04:00
name string
labels opts . ListOpts
containerLabels opts . ListOpts
image string
2016-12-07 14:37:55 -05:00
entrypoint ShlexOpt
2016-09-08 13:11:39 -04:00
args [ ] string
2016-10-27 07:44:19 -04:00
hostname string
2016-09-08 13:11:39 -04:00
env opts . ListOpts
2016-07-20 02:58:32 -04:00
envFile opts . ListOpts
2016-09-08 13:11:39 -04:00
workdir string
user string
2016-11-08 10:06:07 -05:00
groups opts . ListOpts
2017-03-29 18:55:21 -04:00
credentialSpec credentialSpecOpt
2018-06-14 07:50:12 -04:00
init bool
2017-02-06 00:22:57 -05:00
stopSignal string
2016-11-04 14:31:44 -04:00
tty bool
2017-01-14 03:12:19 -05:00
readOnly bool
2016-10-24 23:26:54 -04:00
mounts opts . MountOpt
2016-10-19 20:07:44 -04:00
dns opts . ListOpts
dnsSearch opts . ListOpts
2016-11-08 21:29:10 -05:00
dnsOption opts . ListOpts
2016-11-03 11:05:00 -04:00
hosts opts . ListOpts
2019-02-12 10:07:07 -05:00
sysctls opts . ListOpts
2020-07-29 08:35:51 -04:00
capAdd opts . ListOpts
capDrop opts . ListOpts
2016-09-08 13:11:39 -04:00
resources resourceOptions
2017-05-16 11:49:40 -04:00
stopGrace opts . DurationOpt
2016-09-08 13:11:39 -04:00
2020-01-09 13:17:43 -05:00
replicas Uint64Opt
mode string
maxConcurrent Uint64Opt
2016-09-08 13:11:39 -04:00
2017-01-19 18:27:37 -05:00
restartPolicy restartPolicyOptions
constraints opts . ListOpts
placementPrefs placementPrefOpts
2019-01-09 12:10:35 -05:00
maxReplicas uint64
2017-01-19 18:27:37 -05:00
update updateOptions
2017-02-15 19:04:30 -05:00
rollback updateOptions
2017-05-09 19:29:04 -04:00
networks opts . NetworkOpt
2017-01-19 18:27:37 -05:00
endpoint endpointOptions
2016-09-08 13:11:39 -04:00
2017-05-15 19:01:48 -04:00
registryAuth bool
noResolveImage bool
2016-09-08 13:11:39 -04:00
logDriver logDriverOptions
2016-10-13 14:28:32 -04:00
healthcheck healthCheckOptions
2016-11-03 11:08:22 -04:00
secrets opts . SecretOpt
2017-05-08 13:36:04 -04:00
configs opts . ConfigOpt
2017-11-17 09:31:13 -05:00
isolation string
2016-09-08 13:11:39 -04:00
}
func newServiceOptions ( ) * serviceOptions {
return & serviceOptions {
Fix labels copying value from environment variables
This patch fixes a bug where labels use the same behavior as `--env`, resulting
in a value to be copied from environment variables with the same name as the
label if no value is set (i.e. a simple key, no `=` sign, no value).
An earlier pull request addressed similar cases for `docker run`;
2b17f4c8a8caad552025edb05a73db683fb8a5c6, but this did not address the
same situation for (e.g.) `docker service create`.
Digging in history for this bug, I found that use of the `ValidateEnv`
function for labels was added in the original implementation of the labels feature in
https://github.com/docker/docker/commit/abb5e9a0777469e64fe2c7ecfa66ea01083d2071#diff-ae476143d40e21ac0918630f7365ed3cR34
However, the design never intended it to expand environment variables,
and use of this function was either due to either a "copy/paste" of the
equivalent `--env` flags, or a misunderstanding (the name `ValidateEnv` does
not communicate that it also expands environment variables), and the existing
`ValidateLabel` was designed for _engine_ labels (which required a value to
be set).
Following the initial implementation, other parts of the code followed
the same (incorrect) approach, therefore leading the bug to be introduced
in services as well.
This patch:
- updates the `ValidateLabel` to match the expected validation
rules (this function is no longer used since 31dc5c0a9a8bdc11c7ad335aebb753ed527caa5a),
and the daemon has its own implementation)
- corrects various locations in the code where `ValidateEnv` was used instead of `ValidateLabel`.
Before this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker service inspect --format '{{json .Spec.Labels}}' test
{"SOME_ENV_VAR":"I_AM_SOME_ENV_VAR"}
```
After this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker container inspect --format '{{json .Config.Labels}}' test
{"SOME_ENV_VAR":""}
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-02-13 10:47:30 -05:00
labels : opts . NewListOpts ( opts . ValidateLabel ) ,
2016-11-08 10:06:07 -05:00
constraints : opts . NewListOpts ( nil ) ,
Fix labels copying value from environment variables
This patch fixes a bug where labels use the same behavior as `--env`, resulting
in a value to be copied from environment variables with the same name as the
label if no value is set (i.e. a simple key, no `=` sign, no value).
An earlier pull request addressed similar cases for `docker run`;
2b17f4c8a8caad552025edb05a73db683fb8a5c6, but this did not address the
same situation for (e.g.) `docker service create`.
Digging in history for this bug, I found that use of the `ValidateEnv`
function for labels was added in the original implementation of the labels feature in
https://github.com/docker/docker/commit/abb5e9a0777469e64fe2c7ecfa66ea01083d2071#diff-ae476143d40e21ac0918630f7365ed3cR34
However, the design never intended it to expand environment variables,
and use of this function was either due to either a "copy/paste" of the
equivalent `--env` flags, or a misunderstanding (the name `ValidateEnv` does
not communicate that it also expands environment variables), and the existing
`ValidateLabel` was designed for _engine_ labels (which required a value to
be set).
Following the initial implementation, other parts of the code followed
the same (incorrect) approach, therefore leading the bug to be introduced
in services as well.
This patch:
- updates the `ValidateLabel` to match the expected validation
rules (this function is no longer used since 31dc5c0a9a8bdc11c7ad335aebb753ed527caa5a),
and the daemon has its own implementation)
- corrects various locations in the code where `ValidateEnv` was used instead of `ValidateLabel`.
Before this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker service inspect --format '{{json .Spec.Labels}}' test
{"SOME_ENV_VAR":"I_AM_SOME_ENV_VAR"}
```
After this patch:
```bash
export SOME_ENV_VAR=I_AM_SOME_ENV_VAR
docker service create --label SOME_ENV_VAR --tty --name test busybox
docker container inspect --format '{{json .Config.Labels}}' test
{"SOME_ENV_VAR":""}
```
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-02-13 10:47:30 -05:00
containerLabels : opts . NewListOpts ( opts . ValidateLabel ) ,
2016-12-23 14:09:12 -05:00
env : opts . NewListOpts ( opts . ValidateEnv ) ,
2016-07-20 02:58:32 -04:00
envFile : opts . NewListOpts ( nil ) ,
2016-12-08 16:32:10 -05:00
groups : opts . NewListOpts ( nil ) ,
logDriver : newLogDriverOptions ( ) ,
dns : opts . NewListOpts ( opts . ValidateIPAddress ) ,
dnsOption : opts . NewListOpts ( nil ) ,
dnsSearch : opts . NewListOpts ( opts . ValidateDNSSearch ) ,
2016-12-23 14:09:12 -05:00
hosts : opts . NewListOpts ( opts . ValidateExtraHost ) ,
2019-02-12 10:07:07 -05:00
sysctls : opts . NewListOpts ( nil ) ,
2020-07-29 08:35:51 -04:00
capAdd : opts . NewListOpts ( nil ) ,
capDrop : opts . NewListOpts ( nil ) ,
2016-09-08 13:11:39 -04:00
}
}
2017-06-05 18:23:21 -04:00
func ( options * serviceOptions ) ToServiceMode ( ) ( swarm . ServiceMode , error ) {
2017-01-16 03:58:23 -05:00
serviceMode := swarm . ServiceMode { }
2017-06-05 18:23:21 -04:00
switch options . mode {
2017-01-16 03:58:23 -05:00
case "global" :
2017-06-05 18:23:21 -04:00
if options . replicas . Value ( ) != nil {
2020-01-09 13:17:43 -05:00
return serviceMode , errors . Errorf ( "replicas can only be used with replicated or replicated-job mode" )
2017-01-16 03:58:23 -05:00
}
2019-01-09 12:10:35 -05:00
if options . maxReplicas > 0 {
2020-01-09 13:17:43 -05:00
return serviceMode , errors . New ( "replicas-max-per-node can only be used with replicated or replicated-job mode" )
}
if options . maxConcurrent . Value ( ) != nil {
return serviceMode , errors . New ( "max-concurrent can only be used with replicated-job mode" )
2019-01-09 12:10:35 -05:00
}
2017-01-16 03:58:23 -05:00
serviceMode . Global = & swarm . GlobalService { }
case "replicated" :
2020-01-09 13:17:43 -05:00
if options . maxConcurrent . Value ( ) != nil {
return serviceMode , errors . New ( "max-concurrent can only be used with replicated-job mode" )
}
2017-01-16 03:58:23 -05:00
serviceMode . Replicated = & swarm . ReplicatedService {
2017-06-05 18:23:21 -04:00
Replicas : options . replicas . Value ( ) ,
2017-01-16 03:58:23 -05:00
}
2020-01-09 13:17:43 -05:00
case "replicated-job" :
concurrent := options . maxConcurrent . Value ( )
if concurrent == nil {
concurrent = options . replicas . Value ( )
}
serviceMode . ReplicatedJob = & swarm . ReplicatedJob {
MaxConcurrent : concurrent ,
TotalCompletions : options . replicas . Value ( ) ,
}
case "global-job" :
if options . maxReplicas > 0 {
return serviceMode , errors . New ( "replicas-max-per-node can only be used with replicated or replicated-job mode" )
}
if options . maxConcurrent . Value ( ) != nil {
return serviceMode , errors . New ( "max-concurrent can only be used with replicated-job mode" )
}
if options . replicas . Value ( ) != nil {
return serviceMode , errors . Errorf ( "replicas can only be used with replicated or replicated-job mode" )
}
serviceMode . GlobalJob = & swarm . GlobalJob { }
2017-01-16 03:58:23 -05:00
default :
2017-06-05 18:23:21 -04:00
return serviceMode , errors . Errorf ( "Unknown mode: %s, only replicated and global supported" , options . mode )
2017-01-16 03:58:23 -05:00
}
return serviceMode , nil
}
2017-06-05 18:23:21 -04:00
func ( options * serviceOptions ) ToStopGracePeriod ( flags * pflag . FlagSet ) * time . Duration {
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagStopGracePeriod ) {
2017-06-05 18:23:21 -04:00
return options . stopGrace . Value ( )
2017-03-30 21:35:04 -04:00
}
return nil
}
2020-01-09 13:17:43 -05:00
// makeEnv gets the environment variables from the command line options and
// returns a slice of strings to use in the service spec when doing ToService
func ( options * serviceOptions ) makeEnv ( ) ( [ ] string , error ) {
2018-01-26 12:45:29 -05:00
envVariables , err := opts . ReadKVEnvStrings ( options . envFile . GetAll ( ) , options . env . GetAll ( ) )
2016-07-20 02:58:32 -04:00
if err != nil {
2020-01-09 13:17:43 -05:00
return nil , err
2016-07-20 02:58:32 -04:00
}
currentEnv := make ( [ ] string , 0 , len ( envVariables ) )
for _ , env := range envVariables { // need to process each var, in order
k := strings . SplitN ( env , "=" , 2 ) [ 0 ]
for i , current := range currentEnv { // remove duplicates
if current == env {
continue // no update required, may hide this behind flag to preserve order of envVariables
}
if strings . HasPrefix ( current , k + "=" ) {
currentEnv = append ( currentEnv [ : i ] , currentEnv [ i + 1 : ] ... )
}
}
currentEnv = append ( currentEnv , env )
}
2020-01-09 13:17:43 -05:00
return currentEnv , nil
}
// ToService takes the set of flags passed to the command and converts them
// into a service spec.
//
// Takes an API client as the second argument in order to resolve network names
// from the flags into network IDs.
//
// Returns an error if any flags are invalid or contradictory.
func ( options * serviceOptions ) ToService ( ctx context . Context , apiClient client . NetworkAPIClient , flags * pflag . FlagSet ) ( swarm . ServiceSpec , error ) {
var service swarm . ServiceSpec
currentEnv , err := options . makeEnv ( )
if err != nil {
return service , err
}
2017-06-05 18:23:21 -04:00
healthConfig , err := options . healthcheck . toHealthConfig ( )
2017-01-16 03:58:23 -05:00
if err != nil {
return service , err
}
2017-06-05 18:23:21 -04:00
serviceMode , err := options . ToServiceMode ( )
2017-01-16 03:58:23 -05:00
if err != nil {
return service , err
}
2020-01-09 13:17:43 -05:00
updateConfig := options . update . updateConfig ( flags )
rollbackConfig := options . rollback . rollbackConfig ( flags )
// update and rollback configuration is not supported for jobs. If these
// flags are not set, then the values will be nil. If they are non-nil,
// then return an error.
if ( serviceMode . ReplicatedJob != nil || serviceMode . GlobalJob != nil ) && ( updateConfig != nil || rollbackConfig != nil ) {
return service , errors . Errorf ( "update and rollback configuration is not supported for jobs" )
}
2018-01-03 09:40:55 -05:00
networks := convertNetworks ( options . networks )
for i , net := range networks {
nwID , err := resolveNetworkID ( ctx , apiClient , net . Target )
if err != nil {
return service , err
}
networks [ i ] . Target = nwID
2017-03-23 20:51:57 -04:00
}
2018-07-06 15:49:10 -04:00
sort . Slice ( networks , func ( i , j int ) bool {
return networks [ i ] . Target < networks [ j ] . Target
} )
2017-03-23 20:51:57 -04:00
2017-11-08 11:33:36 -05:00
resources , err := options . resources . ToResourceRequirements ( )
if err != nil {
return service , err
}
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
capAdd , capDrop := opts . EffectiveCapAddCapDrop ( options . capAdd . GetAll ( ) , options . capDrop . GetAll ( ) )
2016-09-08 13:11:39 -04:00
service = swarm . ServiceSpec {
Annotations : swarm . Annotations {
2017-06-05 18:23:21 -04:00
Name : options . name ,
Labels : opts . ConvertKVStringsToMap ( options . labels . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
} ,
TaskTemplate : swarm . TaskSpec {
2017-08-07 05:52:40 -04:00
ContainerSpec : & swarm . ContainerSpec {
2017-06-05 18:23:21 -04:00
Image : options . image ,
Args : options . args ,
Command : options . entrypoint . Value ( ) ,
2017-02-06 00:22:57 -05:00
Env : currentEnv ,
2017-06-05 18:23:21 -04:00
Hostname : options . hostname ,
Labels : opts . ConvertKVStringsToMap ( options . containerLabels . GetAll ( ) ) ,
Dir : options . workdir ,
User : options . user ,
Groups : options . groups . GetAll ( ) ,
StopSignal : options . stopSignal ,
TTY : options . tty ,
ReadOnly : options . readOnly ,
Mounts : options . mounts . Value ( ) ,
2018-06-14 07:50:12 -04:00
Init : & options . init ,
2016-10-19 20:07:44 -04:00
DNSConfig : & swarm . DNSConfig {
2017-06-05 18:23:21 -04:00
Nameservers : options . dns . GetAll ( ) ,
Search : options . dnsSearch . GetAll ( ) ,
Options : options . dnsOption . GetAll ( ) ,
2016-10-19 20:07:44 -04:00
} ,
2017-06-05 18:23:21 -04:00
Hosts : convertExtraHostsToSwarmHosts ( options . hosts . GetAll ( ) ) ,
StopGracePeriod : options . ToStopGracePeriod ( flags ) ,
2017-01-16 03:58:23 -05:00
Healthcheck : healthConfig ,
2017-11-17 09:31:13 -05:00
Isolation : container . Isolation ( options . isolation ) ,
2019-02-12 10:07:07 -05:00
Sysctls : opts . ConvertKVStringsToMap ( options . sysctls . GetAll ( ) ) ,
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
CapabilityAdd : capAdd ,
CapabilityDrop : capDrop ,
2016-09-08 13:11:39 -04:00
} ,
2017-03-23 20:51:57 -04:00
Networks : networks ,
2017-11-08 11:33:36 -05:00
Resources : resources ,
2017-06-05 18:23:21 -04:00
RestartPolicy : options . restartPolicy . ToRestartPolicy ( flags ) ,
2016-09-08 13:11:39 -04:00
Placement : & swarm . Placement {
2017-06-05 18:23:21 -04:00
Constraints : options . constraints . GetAll ( ) ,
Preferences : options . placementPrefs . prefs ,
2019-01-09 12:10:35 -05:00
MaxReplicas : options . maxReplicas ,
2016-09-08 13:11:39 -04:00
} ,
2017-06-05 18:23:21 -04:00
LogDriver : options . logDriver . toLogDriver ( ) ,
2016-09-08 13:11:39 -04:00
} ,
2017-02-15 19:04:30 -05:00
Mode : serviceMode ,
2020-01-09 13:17:43 -05:00
UpdateConfig : updateConfig ,
RollbackConfig : rollbackConfig ,
2017-06-05 18:23:21 -04:00
EndpointSpec : options . endpoint . ToEndpointSpec ( ) ,
2016-09-08 13:11:39 -04:00
}
2019-03-27 16:44:32 -04:00
if options . credentialSpec . String ( ) != "" && options . credentialSpec . Value ( ) != nil {
2017-03-29 18:55:21 -04:00
service . TaskTemplate . ContainerSpec . Privileges = & swarm . Privileges {
2017-06-05 18:23:21 -04:00
CredentialSpec : options . credentialSpec . Value ( ) ,
2017-03-29 18:55:21 -04:00
}
}
2016-09-08 13:11:39 -04:00
return service , nil
}
2017-03-30 21:35:04 -04:00
type flagDefaults map [ string ] interface { }
func ( fd flagDefaults ) getUint64 ( flagName string ) uint64 {
if val , ok := fd [ flagName ] . ( uint64 ) ; ok {
return val
}
return 0
}
func ( fd flagDefaults ) getString ( flagName string ) string {
if val , ok := fd [ flagName ] . ( string ) ; ok {
return val
}
return ""
}
func buildServiceDefaultFlagMapping ( ) flagDefaults {
defaultFlagValues := make ( map [ string ] interface { } )
defaultFlagValues [ flagStopGracePeriod ] , _ = gogotypes . DurationFromProto ( defaults . Service . Task . GetContainer ( ) . StopGracePeriod )
defaultFlagValues [ flagRestartCondition ] = ` " ` + defaultRestartCondition ( ) + ` " `
defaultFlagValues [ flagRestartDelay ] , _ = gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Delay )
if defaults . Service . Task . Restart . MaxAttempts != 0 {
defaultFlagValues [ flagRestartMaxAttempts ] = defaults . Service . Task . Restart . MaxAttempts
}
defaultRestartWindow , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Window )
if defaultRestartWindow != 0 {
defaultFlagValues [ flagRestartWindow ] = defaultRestartWindow
}
defaultFlagValues [ flagUpdateParallelism ] = defaults . Service . Update . Parallelism
defaultFlagValues [ flagUpdateDelay ] = defaults . Service . Update . Delay
defaultFlagValues [ flagUpdateMonitor ] , _ = gogotypes . DurationFromProto ( defaults . Service . Update . Monitor )
defaultFlagValues [ flagUpdateFailureAction ] = ` " ` + strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaults . Service . Update . FailureAction ) ] ) + ` " `
defaultFlagValues [ flagUpdateMaxFailureRatio ] = defaults . Service . Update . MaxFailureRatio
defaultFlagValues [ flagUpdateOrder ] = ` " ` + defaultOrder ( defaults . Service . Update . Order ) + ` " `
defaultFlagValues [ flagRollbackParallelism ] = defaults . Service . Rollback . Parallelism
defaultFlagValues [ flagRollbackDelay ] = defaults . Service . Rollback . Delay
defaultFlagValues [ flagRollbackMonitor ] , _ = gogotypes . DurationFromProto ( defaults . Service . Rollback . Monitor )
defaultFlagValues [ flagRollbackFailureAction ] = ` " ` + strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaults . Service . Rollback . FailureAction ) ] ) + ` " `
defaultFlagValues [ flagRollbackMaxFailureRatio ] = defaults . Service . Rollback . MaxFailureRatio
defaultFlagValues [ flagRollbackOrder ] = ` " ` + defaultOrder ( defaults . Service . Rollback . Order ) + ` " `
defaultFlagValues [ flagEndpointMode ] = "vip"
return defaultFlagValues
}
2017-06-26 21:19:36 -04:00
func addDetachFlag ( flags * pflag . FlagSet , detach * bool ) {
Use non-detached mode as default for service commands
Commit 330a0035334871d92207b583c1c36d52a244753f added a `--detach=false` option
to various service-related commands, with the intent to make this the default in
a future version (17.09).
This patch changes the default to use "interactive" (non-detached), allowing
users to override this by setting the `--detach` option.
To prevent problems when connecting to older daemon versions (17.05 and below,
see commit db60f255617fd90cb093813dcdfe7eec840eeff8), the detach option is
ignored for those versions, and detach is always true.
Before this change, a warning was printed to announce the upcoming default:
$ docker service create nginx:alpine
saxiyn3pe559d753730zr0xer
Since --detach=false was not specified, tasks will be created in the background.
In a future release, --detach=false will become the default.
After this change, no warning is printed, but `--detach` is disabled;
$ docker service create nginx:alpine
y9jujwzozi0hwgj5yaadzliq6
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
Setting the `--detach` flag makes the cli use the pre-17.06 behavior:
$ docker service create --detach nginx:alpine
280hjnzy0wzje5o56gr22a46n
Running against a 17.03 daemon, without specifying the `--detach` flag;
$ docker service create nginx:alpine
kqheg7ogj0kszoa34g4p73i8q
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-09-12 08:35:19 -04:00
flags . BoolVarP ( detach , flagDetach , "d" , false , "Exit immediately instead of waiting for the service to converge" )
2017-06-26 21:19:36 -04:00
flags . SetAnnotation ( flagDetach , "version" , [ ] string { "1.29" } )
}
2016-09-08 13:11:39 -04:00
// addServiceFlags adds all flags that are common to both `create` and `update`.
// Any flags that are not common are added separately in the individual command
2017-03-30 21:35:04 -04:00
func addServiceFlags ( flags * pflag . FlagSet , opts * serviceOptions , defaultFlagValues flagDefaults ) {
flagDesc := func ( flagName string , desc string ) string {
if defaultValue , ok := defaultFlagValues [ flagName ] ; ok {
return fmt . Sprintf ( "%s (default %v)" , desc , defaultValue )
}
return desc
}
2017-06-26 21:19:36 -04:00
addDetachFlag ( flags , & opts . detach )
flags . BoolVarP ( & opts . quiet , flagQuiet , "q" , false , "Suppress progress output" )
2017-02-16 20:05:36 -05:00
2016-09-08 13:11:39 -04:00
flags . StringVarP ( & opts . workdir , flagWorkdir , "w" , "" , "Working directory inside the container" )
flags . StringVarP ( & opts . user , flagUser , "u" , "" , "Username or UID (format: <name|uid>[:<group|gid>])" )
2017-03-29 18:55:21 -04:00
flags . Var ( & opts . credentialSpec , flagCredentialSpec , "Credential spec for managed service account (Windows only)" )
flags . SetAnnotation ( flagCredentialSpec , "version" , [ ] string { "1.29" } )
2016-11-23 14:42:56 -05:00
flags . StringVar ( & opts . hostname , flagHostname , "" , "Container hostname" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHostname , "version" , [ ] string { "1.25" } )
2016-12-07 14:37:55 -05:00
flags . Var ( & opts . entrypoint , flagEntrypoint , "Overwrite the default ENTRYPOINT of the image" )
2020-07-29 08:35:51 -04:00
flags . Var ( & opts . capAdd , flagCapAdd , "Add Linux capabilities" )
flags . SetAnnotation ( flagCapAdd , "version" , [ ] string { "1.41" } )
flags . Var ( & opts . capDrop , flagCapDrop , "Drop Linux capabilities" )
flags . SetAnnotation ( flagCapDrop , "version" , [ ] string { "1.41" } )
2016-09-08 13:11:39 -04:00
flags . Var ( & opts . resources . limitCPU , flagLimitCPU , "Limit CPUs" )
flags . Var ( & opts . resources . limitMemBytes , flagLimitMemory , "Limit Memory" )
flags . Var ( & opts . resources . resCPU , flagReserveCPU , "Reserve CPUs" )
flags . Var ( & opts . resources . resMemBytes , flagReserveMemory , "Reserve Memory" )
2020-04-29 11:11:48 -04:00
flags . Int64Var ( & opts . resources . limitPids , flagLimitPids , 0 , "Limit maximum number of processes (default 0 = unlimited)" )
flags . SetAnnotation ( flagLimitPids , "version" , [ ] string { "1.41" } )
flags . SetAnnotation ( flagLimitPids , "swarm" , nil )
2016-09-08 13:11:39 -04:00
2017-03-30 21:35:04 -04:00
flags . Var ( & opts . stopGrace , flagStopGracePeriod , flagDesc ( flagStopGracePeriod , "Time to wait before force killing a container (ns|us|ms|s|m|h)" ) )
2016-09-08 13:11:39 -04:00
flags . Var ( & opts . replicas , flagReplicas , "Number of tasks" )
2020-01-09 13:17:43 -05:00
flags . Var ( & opts . maxConcurrent , flagConcurrent , "Number of job tasks to run concurrently (default equal to --replicas)" )
flags . SetAnnotation ( flagConcurrent , "version" , [ ] string { "1.41" } )
2019-01-09 12:10:35 -05:00
flags . Uint64Var ( & opts . maxReplicas , flagMaxReplicas , defaultFlagValues . getUint64 ( flagMaxReplicas ) , "Maximum number of tasks per node (default 0 = unlimited)" )
flags . SetAnnotation ( flagMaxReplicas , "version" , [ ] string { "1.40" } )
2016-09-08 13:11:39 -04:00
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . restartPolicy . condition , flagRestartCondition , "" , flagDesc ( flagRestartCondition , ` Restart when condition is met ("none"|"on-failure"|"any") ` ) )
flags . Var ( & opts . restartPolicy . delay , flagRestartDelay , flagDesc ( flagRestartDelay , "Delay between restart attempts (ns|us|ms|s|m|h)" ) )
flags . Var ( & opts . restartPolicy . maxAttempts , flagRestartMaxAttempts , flagDesc ( flagRestartMaxAttempts , "Maximum number of restarts before giving up" ) )
flags . Var ( & opts . restartPolicy . window , flagRestartWindow , flagDesc ( flagRestartWindow , "Window used to evaluate the restart policy (ns|us|ms|s|m|h)" ) )
2016-09-08 13:11:39 -04:00
2017-03-30 21:35:04 -04:00
flags . Uint64Var ( & opts . update . parallelism , flagUpdateParallelism , defaultFlagValues . getUint64 ( flagUpdateParallelism ) , "Maximum number of tasks updated simultaneously (0 to update all at once)" )
flags . DurationVar ( & opts . update . delay , flagUpdateDelay , 0 , flagDesc ( flagUpdateDelay , "Delay between updates (ns|us|ms|s|m|h)" ) )
flags . DurationVar ( & opts . update . monitor , flagUpdateMonitor , 0 , flagDesc ( flagUpdateMonitor , "Duration after each task update to monitor for failure (ns|us|ms|s|m|h)" ) )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagUpdateMonitor , "version" , [ ] string { "1.25" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . update . onFailure , flagUpdateFailureAction , "" , flagDesc ( flagUpdateFailureAction , ` Action on update failure ("pause"|"continue"|"rollback") ` ) )
flags . Var ( & opts . update . maxFailureRatio , flagUpdateMaxFailureRatio , flagDesc ( flagUpdateMaxFailureRatio , "Failure rate to tolerate during an update" ) )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagUpdateMaxFailureRatio , "version" , [ ] string { "1.25" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . update . order , flagUpdateOrder , "" , flagDesc ( flagUpdateOrder , ` Update order ("start-first"|"stop-first") ` ) )
2017-01-18 14:38:19 -05:00
flags . SetAnnotation ( flagUpdateOrder , "version" , [ ] string { "1.29" } )
2016-09-08 13:11:39 -04:00
2017-05-03 18:14:30 -04:00
flags . Uint64Var ( & opts . rollback . parallelism , flagRollbackParallelism , defaultFlagValues . getUint64 ( flagRollbackParallelism ) ,
"Maximum number of tasks rolled back simultaneously (0 to roll back all at once)" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackParallelism , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . DurationVar ( & opts . rollback . delay , flagRollbackDelay , 0 , flagDesc ( flagRollbackDelay , "Delay between task rollbacks (ns|us|ms|s|m|h)" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackDelay , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . DurationVar ( & opts . rollback . monitor , flagRollbackMonitor , 0 , flagDesc ( flagRollbackMonitor , "Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h)" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackMonitor , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . rollback . onFailure , flagRollbackFailureAction , "" , flagDesc ( flagRollbackFailureAction , ` Action on rollback failure ("pause"|"continue") ` ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackFailureAction , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . Var ( & opts . rollback . maxFailureRatio , flagRollbackMaxFailureRatio , flagDesc ( flagRollbackMaxFailureRatio , "Failure rate to tolerate during a rollback" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackMaxFailureRatio , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . rollback . order , flagRollbackOrder , "" , flagDesc ( flagRollbackOrder , ` Rollback order ("start-first"|"stop-first") ` ) )
2017-01-18 14:38:19 -05:00
flags . SetAnnotation ( flagRollbackOrder , "version" , [ ] string { "1.29" } )
2017-02-15 19:04:30 -05:00
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . endpoint . mode , flagEndpointMode , defaultFlagValues . getString ( flagEndpointMode ) , "Endpoint mode (vip or dnsrr)" )
2016-09-08 13:11:39 -04:00
flags . BoolVar ( & opts . registryAuth , flagRegistryAuth , false , "Send registry authentication details to swarm agents" )
2017-05-15 19:01:48 -04:00
flags . BoolVar ( & opts . noResolveImage , flagNoResolveImage , false , "Do not query the registry to resolve image digest and supported platforms" )
flags . SetAnnotation ( flagNoResolveImage , "version" , [ ] string { "1.30" } )
2016-09-08 13:11:39 -04:00
flags . StringVar ( & opts . logDriver . name , flagLogDriver , "" , "Logging driver for service" )
flags . Var ( & opts . logDriver . opts , flagLogOpt , "Logging driver options" )
2016-10-13 14:28:32 -04:00
flags . StringVar ( & opts . healthcheck . cmd , flagHealthCmd , "" , "Command to run to check health" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthCmd , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . interval , flagHealthInterval , "Time between running the check (ms|s|m|h)" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthInterval , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . timeout , flagHealthTimeout , "Maximum time to allow one check to run (ms|s|m|h)" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthTimeout , "version" , [ ] string { "1.25" } )
2016-10-13 14:28:32 -04:00
flags . IntVar ( & opts . healthcheck . retries , flagHealthRetries , 0 , "Consecutive failures needed to report unhealthy" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthRetries , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . startPeriod , flagHealthStartPeriod , "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)" )
2016-11-29 04:58:47 -05:00
flags . SetAnnotation ( flagHealthStartPeriod , "version" , [ ] string { "1.29" } )
2016-10-13 14:28:32 -04:00
flags . BoolVar ( & opts . healthcheck . noHealthcheck , flagNoHealthcheck , false , "Disable any container-specified HEALTHCHECK" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagNoHealthcheck , "version" , [ ] string { "1.25" } )
2016-11-04 14:31:44 -04:00
flags . BoolVarP ( & opts . tty , flagTTY , "t" , false , "Allocate a pseudo-TTY" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagTTY , "version" , [ ] string { "1.25" } )
2017-01-14 03:12:19 -05:00
flags . BoolVar ( & opts . readOnly , flagReadOnly , false , "Mount the container's root filesystem as read only" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagReadOnly , "version" , [ ] string { "1.28" } )
2017-02-06 00:22:57 -05:00
flags . StringVar ( & opts . stopSignal , flagStopSignal , "" , "Signal to stop the container" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagStopSignal , "version" , [ ] string { "1.28" } )
2017-11-17 09:31:13 -05:00
flags . StringVar ( & opts . isolation , flagIsolation , "" , "Service container isolation mode" )
flags . SetAnnotation ( flagIsolation , "version" , [ ] string { "1.35" } )
2016-09-08 13:11:39 -04:00
}
const (
2017-03-29 18:55:21 -04:00
flagCredentialSpec = "credential-spec"
2017-02-15 19:04:30 -05:00
flagPlacementPref = "placement-pref"
flagPlacementPrefAdd = "placement-pref-add"
flagPlacementPrefRemove = "placement-pref-rm"
flagConstraint = "constraint"
flagConstraintRemove = "constraint-rm"
flagConstraintAdd = "constraint-add"
flagContainerLabel = "container-label"
flagContainerLabelRemove = "container-label-rm"
flagContainerLabelAdd = "container-label-add"
2017-06-26 21:19:36 -04:00
flagDetach = "detach"
2017-02-15 19:04:30 -05:00
flagDNS = "dns"
flagDNSRemove = "dns-rm"
flagDNSAdd = "dns-add"
flagDNSOption = "dns-option"
flagDNSOptionRemove = "dns-option-rm"
flagDNSOptionAdd = "dns-option-add"
flagDNSSearch = "dns-search"
flagDNSSearchRemove = "dns-search-rm"
flagDNSSearchAdd = "dns-search-add"
flagEndpointMode = "endpoint-mode"
2016-12-07 14:37:55 -05:00
flagEntrypoint = "entrypoint"
2017-02-15 19:04:30 -05:00
flagEnv = "env"
flagEnvFile = "env-file"
flagEnvRemove = "env-rm"
flagEnvAdd = "env-add"
2017-11-17 17:05:44 -05:00
flagGenericResourcesRemove = "generic-resource-rm"
flagGenericResourcesAdd = "generic-resource-add"
2017-02-15 19:04:30 -05:00
flagGroup = "group"
flagGroupAdd = "group-add"
flagGroupRemove = "group-rm"
2017-06-26 21:19:36 -04:00
flagHost = "host"
flagHostAdd = "host-add"
flagHostRemove = "host-rm"
flagHostname = "hostname"
2017-02-15 19:04:30 -05:00
flagLabel = "label"
flagLabelRemove = "label-rm"
flagLabelAdd = "label-add"
flagLimitCPU = "limit-cpu"
flagLimitMemory = "limit-memory"
2020-04-29 11:11:48 -04:00
flagLimitPids = "limit-pids"
2019-01-09 12:10:35 -05:00
flagMaxReplicas = "replicas-max-per-node"
2020-01-09 13:17:43 -05:00
flagConcurrent = "max-concurrent"
2017-02-15 19:04:30 -05:00
flagMode = "mode"
flagMount = "mount"
flagMountRemove = "mount-rm"
flagMountAdd = "mount-add"
flagName = "name"
flagNetwork = "network"
2017-03-23 20:51:57 -04:00
flagNetworkAdd = "network-add"
flagNetworkRemove = "network-rm"
2017-02-15 19:04:30 -05:00
flagPublish = "publish"
flagPublishRemove = "publish-rm"
flagPublishAdd = "publish-add"
2017-06-26 21:19:36 -04:00
flagQuiet = "quiet"
2017-02-15 19:04:30 -05:00
flagReadOnly = "read-only"
flagReplicas = "replicas"
flagReserveCPU = "reserve-cpu"
flagReserveMemory = "reserve-memory"
flagRestartCondition = "restart-condition"
flagRestartDelay = "restart-delay"
flagRestartMaxAttempts = "restart-max-attempts"
flagRestartWindow = "restart-window"
2017-06-26 21:19:36 -04:00
flagRollback = "rollback"
2017-02-15 19:04:30 -05:00
flagRollbackDelay = "rollback-delay"
flagRollbackFailureAction = "rollback-failure-action"
flagRollbackMaxFailureRatio = "rollback-max-failure-ratio"
flagRollbackMonitor = "rollback-monitor"
2017-01-18 14:38:19 -05:00
flagRollbackOrder = "rollback-order"
2017-02-15 19:04:30 -05:00
flagRollbackParallelism = "rollback-parallelism"
2018-06-14 07:50:12 -04:00
flagInit = "init"
2019-02-12 10:07:07 -05:00
flagSysCtl = "sysctl"
flagSysCtlAdd = "sysctl-add"
flagSysCtlRemove = "sysctl-rm"
2017-02-15 19:04:30 -05:00
flagStopGracePeriod = "stop-grace-period"
flagStopSignal = "stop-signal"
flagTTY = "tty"
flagUpdateDelay = "update-delay"
flagUpdateFailureAction = "update-failure-action"
flagUpdateMaxFailureRatio = "update-max-failure-ratio"
flagUpdateMonitor = "update-monitor"
2017-01-18 14:38:19 -05:00
flagUpdateOrder = "update-order"
2017-02-15 19:04:30 -05:00
flagUpdateParallelism = "update-parallelism"
flagUser = "user"
flagWorkdir = "workdir"
flagRegistryAuth = "with-registry-auth"
2017-05-15 19:01:48 -04:00
flagNoResolveImage = "no-resolve-image"
2017-02-15 19:04:30 -05:00
flagLogDriver = "log-driver"
flagLogOpt = "log-opt"
flagHealthCmd = "health-cmd"
flagHealthInterval = "health-interval"
flagHealthRetries = "health-retries"
flagHealthTimeout = "health-timeout"
2016-11-29 04:58:47 -05:00
flagHealthStartPeriod = "health-start-period"
2017-02-15 19:04:30 -05:00
flagNoHealthcheck = "no-healthcheck"
flagSecret = "secret"
flagSecretAdd = "secret-add"
flagSecretRemove = "secret-rm"
2017-05-08 13:36:04 -04:00
flagConfig = "config"
flagConfigAdd = "config-add"
flagConfigRemove = "config-rm"
2017-11-17 09:31:13 -05:00
flagIsolation = "isolation"
2020-07-29 08:35:51 -04:00
flagCapAdd = "cap-add"
flagCapDrop = "cap-drop"
2016-09-08 13:11:39 -04:00
)
2018-10-10 06:18:29 -04:00
func validateAPIVersion ( c swarm . ServiceSpec , serverAPIVersion string ) error {
for _ , m := range c . TaskTemplate . ContainerSpec . Mounts {
if m . BindOptions != nil && m . BindOptions . NonRecursive && versions . LessThan ( serverAPIVersion , "1.40" ) {
return errors . Errorf ( "bind-nonrecursive requires API v1.40 or later" )
}
}
return nil
}