github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/api/client/service/update.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/docker/docker/api/client"
    12  	"github.com/docker/docker/cli"
    13  	"github.com/docker/docker/opts"
    14  	runconfigopts "github.com/docker/docker/runconfig/opts"
    15  	"github.com/docker/engine-api/types"
    16  	"github.com/docker/engine-api/types/swarm"
    17  	"github.com/docker/go-connections/nat"
    18  	shlex "github.com/flynn-archive/go-shlex"
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/pflag"
    21  )
    22  
    23  func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
    24  	opts := newServiceOptions()
    25  
    26  	cmd := &cobra.Command{
    27  		Use:   "update [OPTIONS] SERVICE",
    28  		Short: "更新一个服务",
    29  		Args:  cli.ExactArgs(1),
    30  		RunE: func(cmd *cobra.Command, args []string) error {
    31  			return runUpdate(dockerCli, cmd.Flags(), args[0])
    32  		},
    33  	}
    34  
    35  	flags := cmd.Flags()
    36  	flags.String("image", "", "服务镜像的标签")
    37  	flags.String("args", "", "服务的启动命令")
    38  	addServiceFlags(cmd, opts)
    39  	flags.Var(newListOptsVar(), flagEnvRemove, "删除一个环境变量")
    40  	flags.Var(newListOptsVar(), flagLabelRemove, "通过键值删除一个标签")
    41  	flags.Var(newListOptsVar(), flagContainerLabelRemove, "通过键值删除一个容器的标签")
    42  	flags.Var(newListOptsVar(), flagMountRemove, "通过目标路径删除一个挂载")
    43  	flags.Var(newListOptsVar(), flagPublishRemove, "通过目标端口删除一个对外暴露的端口")
    44  	flags.Var(newListOptsVar(), flagConstraintRemove, "删除一条限制条件")
    45  	flags.Var(&opts.labels, flagLabelAdd, "添加或更新服务标签")
    46  	flags.Var(&opts.containerLabels, flagContainerLabelAdd, "添加或更新容器标签")
    47  	flags.Var(&opts.env, flagEnvAdd, "添加或更新环境变量")
    48  	flags.Var(&opts.mounts, flagMountAdd, "添加或更新一个服务的挂载项")
    49  	flags.StringSliceVar(&opts.constraints, flagConstraintAdd, []string{}, "添加或更新放置策略与限制")
    50  	flags.Var(&opts.endpoint.ports, flagPublishAdd, "添加或更新一个对外暴露的端口")
    51  	return cmd
    52  }
    53  
    54  func newListOptsVar() *opts.ListOpts {
    55  	return opts.NewListOptsRef(&[]string{}, nil)
    56  }
    57  
    58  func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
    59  	apiClient := dockerCli.Client()
    60  	ctx := context.Background()
    61  	updateOpts := types.ServiceUpdateOptions{}
    62  
    63  	service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	err = updateService(flags, &service.Spec)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	// only send auth if flag was set
    74  	sendAuth, err := flags.GetBool(flagRegistryAuth)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	if sendAuth {
    79  		// Retrieve encoded auth token from the image reference
    80  		// This would be the old image if it didn't change in this update
    81  		image := service.Spec.TaskTemplate.ContainerSpec.Image
    82  		encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image)
    83  		if err != nil {
    84  			return err
    85  		}
    86  		updateOpts.EncodedRegistryAuth = encodedAuth
    87  	}
    88  
    89  	err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts)
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
    95  	return nil
    96  }
    97  
    98  func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
    99  	updateString := func(flag string, field *string) {
   100  		if flags.Changed(flag) {
   101  			*field, _ = flags.GetString(flag)
   102  		}
   103  	}
   104  
   105  	updateInt64Value := func(flag string, field *int64) {
   106  		if flags.Changed(flag) {
   107  			*field = flags.Lookup(flag).Value.(int64Value).Value()
   108  		}
   109  	}
   110  
   111  	updateDuration := func(flag string, field *time.Duration) {
   112  		if flags.Changed(flag) {
   113  			*field, _ = flags.GetDuration(flag)
   114  		}
   115  	}
   116  
   117  	updateDurationOpt := func(flag string, field **time.Duration) {
   118  		if flags.Changed(flag) {
   119  			val := *flags.Lookup(flag).Value.(*DurationOpt).Value()
   120  			*field = &val
   121  		}
   122  	}
   123  
   124  	updateUint64 := func(flag string, field *uint64) {
   125  		if flags.Changed(flag) {
   126  			*field, _ = flags.GetUint64(flag)
   127  		}
   128  	}
   129  
   130  	updateUint64Opt := func(flag string, field **uint64) {
   131  		if flags.Changed(flag) {
   132  			val := *flags.Lookup(flag).Value.(*Uint64Opt).Value()
   133  			*field = &val
   134  		}
   135  	}
   136  
   137  	cspec := &spec.TaskTemplate.ContainerSpec
   138  	task := &spec.TaskTemplate
   139  
   140  	taskResources := func() *swarm.ResourceRequirements {
   141  		if task.Resources == nil {
   142  			task.Resources = &swarm.ResourceRequirements{}
   143  		}
   144  		return task.Resources
   145  	}
   146  
   147  	updateString(flagName, &spec.Name)
   148  	updateLabels(flags, &spec.Labels)
   149  	updateContainerLabels(flags, &cspec.Labels)
   150  	updateString("image", &cspec.Image)
   151  	updateStringToSlice(flags, "args", &cspec.Args)
   152  	updateEnvironment(flags, &cspec.Env)
   153  	updateString("workdir", &cspec.Dir)
   154  	updateString(flagUser, &cspec.User)
   155  	updateMounts(flags, &cspec.Mounts)
   156  
   157  	if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
   158  		taskResources().Limits = &swarm.Resources{}
   159  		updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
   160  		updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
   161  	}
   162  	if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) {
   163  		taskResources().Reservations = &swarm.Resources{}
   164  		updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
   165  		updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
   166  	}
   167  
   168  	updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
   169  
   170  	if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
   171  		if task.RestartPolicy == nil {
   172  			task.RestartPolicy = &swarm.RestartPolicy{}
   173  		}
   174  
   175  		if flags.Changed(flagRestartCondition) {
   176  			value, _ := flags.GetString(flagRestartCondition)
   177  			task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
   178  		}
   179  		updateDurationOpt(flagRestartDelay, &task.RestartPolicy.Delay)
   180  		updateUint64Opt(flagRestartMaxAttempts, &task.RestartPolicy.MaxAttempts)
   181  		updateDurationOpt(flagRestartWindow, &task.RestartPolicy.Window)
   182  	}
   183  
   184  	if anyChanged(flags, flagConstraintAdd, flagConstraintRemove) {
   185  		if task.Placement == nil {
   186  			task.Placement = &swarm.Placement{}
   187  		}
   188  		updatePlacement(flags, task.Placement)
   189  	}
   190  
   191  	if err := updateReplicas(flags, &spec.Mode); err != nil {
   192  		return err
   193  	}
   194  
   195  	if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay, flagUpdateFailureAction) {
   196  		if spec.UpdateConfig == nil {
   197  			spec.UpdateConfig = &swarm.UpdateConfig{}
   198  		}
   199  		updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
   200  		updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
   201  		updateString(flagUpdateFailureAction, &spec.UpdateConfig.FailureAction)
   202  	}
   203  
   204  	if flags.Changed(flagEndpointMode) {
   205  		value, _ := flags.GetString(flagEndpointMode)
   206  		if spec.EndpointSpec == nil {
   207  			spec.EndpointSpec = &swarm.EndpointSpec{}
   208  		}
   209  		spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
   210  	}
   211  
   212  	if anyChanged(flags, flagPublishAdd, flagPublishRemove) {
   213  		if spec.EndpointSpec == nil {
   214  			spec.EndpointSpec = &swarm.EndpointSpec{}
   215  		}
   216  		if err := updatePorts(flags, &spec.EndpointSpec.Ports); err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	if err := updateLogDriver(flags, &spec.TaskTemplate); err != nil {
   222  		return err
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  func updateStringToSlice(flags *pflag.FlagSet, flag string, field *[]string) error {
   229  	if !flags.Changed(flag) {
   230  		return nil
   231  	}
   232  
   233  	value, _ := flags.GetString(flag)
   234  	valueSlice, err := shlex.Split(value)
   235  	*field = valueSlice
   236  	return err
   237  }
   238  
   239  func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
   240  	for _, flag := range fields {
   241  		if flags.Changed(flag) {
   242  			return true
   243  		}
   244  	}
   245  	return false
   246  }
   247  
   248  func updatePlacement(flags *pflag.FlagSet, placement *swarm.Placement) {
   249  	field, _ := flags.GetStringSlice(flagConstraintAdd)
   250  	placement.Constraints = append(placement.Constraints, field...)
   251  
   252  	toRemove := buildToRemoveSet(flags, flagConstraintRemove)
   253  	placement.Constraints = removeItems(placement.Constraints, toRemove, itemKey)
   254  }
   255  
   256  func updateContainerLabels(flags *pflag.FlagSet, field *map[string]string) {
   257  	if flags.Changed(flagContainerLabelAdd) {
   258  		if *field == nil {
   259  			*field = map[string]string{}
   260  		}
   261  
   262  		values := flags.Lookup(flagContainerLabelAdd).Value.(*opts.ListOpts).GetAll()
   263  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   264  			(*field)[key] = value
   265  		}
   266  	}
   267  
   268  	if *field != nil && flags.Changed(flagContainerLabelRemove) {
   269  		toRemove := flags.Lookup(flagContainerLabelRemove).Value.(*opts.ListOpts).GetAll()
   270  		for _, label := range toRemove {
   271  			delete(*field, label)
   272  		}
   273  	}
   274  }
   275  
   276  func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
   277  	if flags.Changed(flagLabelAdd) {
   278  		if *field == nil {
   279  			*field = map[string]string{}
   280  		}
   281  
   282  		values := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
   283  		for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
   284  			(*field)[key] = value
   285  		}
   286  	}
   287  
   288  	if *field != nil && flags.Changed(flagLabelRemove) {
   289  		toRemove := flags.Lookup(flagLabelRemove).Value.(*opts.ListOpts).GetAll()
   290  		for _, label := range toRemove {
   291  			delete(*field, label)
   292  		}
   293  	}
   294  }
   295  
   296  func updateEnvironment(flags *pflag.FlagSet, field *[]string) {
   297  	envSet := map[string]string{}
   298  	for _, v := range *field {
   299  		envSet[envKey(v)] = v
   300  	}
   301  	if flags.Changed(flagEnvAdd) {
   302  		value := flags.Lookup(flagEnvAdd).Value.(*opts.ListOpts)
   303  		for _, v := range value.GetAll() {
   304  			envSet[envKey(v)] = v
   305  		}
   306  	}
   307  
   308  	*field = []string{}
   309  	for _, v := range envSet {
   310  		*field = append(*field, v)
   311  	}
   312  
   313  	toRemove := buildToRemoveSet(flags, flagEnvRemove)
   314  	*field = removeItems(*field, toRemove, envKey)
   315  }
   316  
   317  func envKey(value string) string {
   318  	kv := strings.SplitN(value, "=", 2)
   319  	return kv[0]
   320  }
   321  
   322  func itemKey(value string) string {
   323  	return value
   324  }
   325  
   326  func buildToRemoveSet(flags *pflag.FlagSet, flag string) map[string]struct{} {
   327  	var empty struct{}
   328  	toRemove := make(map[string]struct{})
   329  
   330  	if !flags.Changed(flag) {
   331  		return toRemove
   332  	}
   333  
   334  	toRemoveSlice := flags.Lookup(flag).Value.(*opts.ListOpts).GetAll()
   335  	for _, key := range toRemoveSlice {
   336  		toRemove[key] = empty
   337  	}
   338  	return toRemove
   339  }
   340  
   341  func removeItems(
   342  	seq []string,
   343  	toRemove map[string]struct{},
   344  	keyFunc func(string) string,
   345  ) []string {
   346  	newSeq := []string{}
   347  	for _, item := range seq {
   348  		if _, exists := toRemove[keyFunc(item)]; !exists {
   349  			newSeq = append(newSeq, item)
   350  		}
   351  	}
   352  	return newSeq
   353  }
   354  
   355  func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
   356  	if flags.Changed(flagMountAdd) {
   357  		values := flags.Lookup(flagMountAdd).Value.(*MountOpt).Value()
   358  		*mounts = append(*mounts, values...)
   359  	}
   360  	toRemove := buildToRemoveSet(flags, flagMountRemove)
   361  
   362  	newMounts := []swarm.Mount{}
   363  	for _, mount := range *mounts {
   364  		if _, exists := toRemove[mount.Target]; !exists {
   365  			newMounts = append(newMounts, mount)
   366  		}
   367  	}
   368  	*mounts = newMounts
   369  }
   370  
   371  type byPortConfig []swarm.PortConfig
   372  
   373  func (r byPortConfig) Len() int      { return len(r) }
   374  func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
   375  func (r byPortConfig) Less(i, j int) bool {
   376  	// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
   377  	// In updatePorts we already filter out with map so there is duplicate entries
   378  	return portConfigToString(&r[i]) < portConfigToString(&r[j])
   379  }
   380  
   381  func portConfigToString(portConfig *swarm.PortConfig) string {
   382  	protocol := portConfig.Protocol
   383  	if protocol == "" {
   384  		protocol = "tcp"
   385  	}
   386  	return fmt.Sprintf("%v/%s", portConfig.PublishedPort, protocol)
   387  }
   388  
   389  func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
   390  	// The key of the map is `port/protocol`, e.g., `80/tcp`
   391  	portSet := map[string]swarm.PortConfig{}
   392  	// Check to see if there are any conflict in flags.
   393  	if flags.Changed(flagPublishAdd) {
   394  		values := flags.Lookup(flagPublishAdd).Value.(*opts.ListOpts).GetAll()
   395  		ports, portBindings, _ := nat.ParsePortSpecs(values)
   396  
   397  		for port := range ports {
   398  			newConfigs := convertPortToPortConfig(port, portBindings)
   399  			for _, entry := range newConfigs {
   400  				if v, ok := portSet[portConfigToString(&entry)]; ok && v != entry {
   401  					return fmt.Errorf("conflicting port mapping between %v:%v/%s and %v:%v/%s", entry.PublishedPort, entry.TargetPort, entry.Protocol, v.PublishedPort, v.TargetPort, v.Protocol)
   402  				}
   403  				portSet[portConfigToString(&entry)] = entry
   404  			}
   405  		}
   406  	}
   407  
   408  	// Override previous PortConfig in service if there is any duplicate
   409  	for _, entry := range *portConfig {
   410  		if _, ok := portSet[portConfigToString(&entry)]; !ok {
   411  			portSet[portConfigToString(&entry)] = entry
   412  		}
   413  	}
   414  
   415  	toRemove := flags.Lookup(flagPublishRemove).Value.(*opts.ListOpts).GetAll()
   416  	newPorts := []swarm.PortConfig{}
   417  portLoop:
   418  	for _, port := range portSet {
   419  		for _, rawTargetPort := range toRemove {
   420  			targetPort := nat.Port(rawTargetPort)
   421  			if equalPort(targetPort, port) {
   422  				continue portLoop
   423  			}
   424  		}
   425  		newPorts = append(newPorts, port)
   426  	}
   427  	// Sort the PortConfig to avoid unnecessary updates
   428  	sort.Sort(byPortConfig(newPorts))
   429  	*portConfig = newPorts
   430  	return nil
   431  }
   432  
   433  func equalPort(targetPort nat.Port, port swarm.PortConfig) bool {
   434  	return (string(port.Protocol) == targetPort.Proto() &&
   435  		port.TargetPort == uint32(targetPort.Int()))
   436  }
   437  
   438  func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
   439  	if !flags.Changed(flagReplicas) {
   440  		return nil
   441  	}
   442  
   443  	if serviceMode == nil || serviceMode.Replicated == nil {
   444  		return fmt.Errorf("replicas can only be used with replicated mode")
   445  	}
   446  	serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
   447  	return nil
   448  }
   449  
   450  // updateLogDriver updates the log driver only if the log driver flag is set.
   451  // All options will be replaced with those provided on the command line.
   452  func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error {
   453  	if !flags.Changed(flagLogDriver) {
   454  		return nil
   455  	}
   456  
   457  	name, err := flags.GetString(flagLogDriver)
   458  	if err != nil {
   459  		return err
   460  	}
   461  
   462  	if name == "" {
   463  		return nil
   464  	}
   465  
   466  	taskTemplate.LogDriver = &swarm.Driver{
   467  		Name:    name,
   468  		Options: runconfigopts.ConvertKVStringsToMap(flags.Lookup(flagLogOpt).Value.(*opts.ListOpts).GetAll()),
   469  	}
   470  
   471  	return nil
   472  }