github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/daemon/cluster/services.go (about)

     1  package cluster
     2  
     3  import (
     4  	"encoding/base64"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	"github.com/docker/distribution/reference"
    13  	apierrors "github.com/docker/docker/api/errors"
    14  	apitypes "github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/backend"
    16  	types "github.com/docker/docker/api/types/swarm"
    17  	"github.com/docker/docker/daemon/cluster/convert"
    18  	"github.com/docker/docker/daemon/logger"
    19  	"github.com/docker/docker/pkg/ioutils"
    20  	"github.com/docker/docker/pkg/stdcopy"
    21  	swarmapi "github.com/docker/swarmkit/api"
    22  	gogotypes "github.com/gogo/protobuf/types"
    23  	"github.com/pkg/errors"
    24  	"golang.org/x/net/context"
    25  )
    26  
    27  // GetServices returns all services of a managed swarm cluster.
    28  func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Service, error) {
    29  	c.mu.RLock()
    30  	defer c.mu.RUnlock()
    31  
    32  	state := c.currentNodeState()
    33  	if !state.IsActiveManager() {
    34  		return nil, c.errNoManager(state)
    35  	}
    36  
    37  	filters, err := newListServicesFilters(options.Filters)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	ctx, cancel := c.getRequestContext()
    42  	defer cancel()
    43  
    44  	r, err := state.controlClient.ListServices(
    45  		ctx,
    46  		&swarmapi.ListServicesRequest{Filters: filters})
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	services := []types.Service{}
    52  
    53  	for _, service := range r.Services {
    54  		services = append(services, convert.ServiceFromGRPC(*service))
    55  	}
    56  
    57  	return services, nil
    58  }
    59  
    60  // GetService returns a service based on an ID or name.
    61  func (c *Cluster) GetService(input string) (types.Service, error) {
    62  	c.mu.RLock()
    63  	defer c.mu.RUnlock()
    64  
    65  	state := c.currentNodeState()
    66  	if !state.IsActiveManager() {
    67  		return types.Service{}, c.errNoManager(state)
    68  	}
    69  
    70  	ctx, cancel := c.getRequestContext()
    71  	defer cancel()
    72  
    73  	service, err := getService(ctx, state.controlClient, input)
    74  	if err != nil {
    75  		return types.Service{}, err
    76  	}
    77  	return convert.ServiceFromGRPC(*service), nil
    78  }
    79  
    80  // CreateService creates a new service in a managed swarm cluster.
    81  func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (*apitypes.ServiceCreateResponse, error) {
    82  	c.mu.RLock()
    83  	defer c.mu.RUnlock()
    84  
    85  	state := c.currentNodeState()
    86  	if !state.IsActiveManager() {
    87  		return nil, c.errNoManager(state)
    88  	}
    89  
    90  	ctx, cancel := c.getRequestContext()
    91  	defer cancel()
    92  
    93  	err := c.populateNetworkID(ctx, state.controlClient, &s)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	serviceSpec, err := convert.ServiceSpecToGRPC(s)
    99  	if err != nil {
   100  		return nil, apierrors.NewBadRequestError(err)
   101  	}
   102  
   103  	ctnr := serviceSpec.Task.GetContainer()
   104  	if ctnr == nil {
   105  		return nil, errors.New("service does not use container tasks")
   106  	}
   107  
   108  	if encodedAuth != "" {
   109  		ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
   110  	}
   111  
   112  	// retrieve auth config from encoded auth
   113  	authConfig := &apitypes.AuthConfig{}
   114  	if encodedAuth != "" {
   115  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuth))).Decode(authConfig); err != nil {
   116  			logrus.Warnf("invalid authconfig: %v", err)
   117  		}
   118  	}
   119  
   120  	resp := &apitypes.ServiceCreateResponse{}
   121  
   122  	// pin image by digest
   123  	if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" {
   124  		digestImage, err := c.imageWithDigestString(ctx, ctnr.Image, authConfig)
   125  		if err != nil {
   126  			logrus.Warnf("unable to pin image %s to digest: %s", ctnr.Image, err.Error())
   127  			resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", ctnr.Image, err.Error()))
   128  		} else if ctnr.Image != digestImage {
   129  			logrus.Debugf("pinning image %s by digest: %s", ctnr.Image, digestImage)
   130  			ctnr.Image = digestImage
   131  		} else {
   132  			logrus.Debugf("creating service using supplied digest reference %s", ctnr.Image)
   133  		}
   134  	}
   135  
   136  	r, err := state.controlClient.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec})
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	resp.ID = r.Service.ID
   142  	return resp, nil
   143  }
   144  
   145  // UpdateService updates existing service to match new properties.
   146  func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec types.ServiceSpec, encodedAuth string, registryAuthFrom string) (*apitypes.ServiceUpdateResponse, error) {
   147  	c.mu.RLock()
   148  	defer c.mu.RUnlock()
   149  
   150  	state := c.currentNodeState()
   151  	if !state.IsActiveManager() {
   152  		return nil, c.errNoManager(state)
   153  	}
   154  
   155  	ctx, cancel := c.getRequestContext()
   156  	defer cancel()
   157  
   158  	err := c.populateNetworkID(ctx, state.controlClient, &spec)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	serviceSpec, err := convert.ServiceSpecToGRPC(spec)
   164  	if err != nil {
   165  		return nil, apierrors.NewBadRequestError(err)
   166  	}
   167  
   168  	currentService, err := getService(ctx, state.controlClient, serviceIDOrName)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	newCtnr := serviceSpec.Task.GetContainer()
   174  	if newCtnr == nil {
   175  		return nil, errors.New("service does not use container tasks")
   176  	}
   177  
   178  	if encodedAuth != "" {
   179  		newCtnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
   180  	} else {
   181  		// this is needed because if the encodedAuth isn't being updated then we
   182  		// shouldn't lose it, and continue to use the one that was already present
   183  		var ctnr *swarmapi.ContainerSpec
   184  		switch registryAuthFrom {
   185  		case apitypes.RegistryAuthFromSpec, "":
   186  			ctnr = currentService.Spec.Task.GetContainer()
   187  		case apitypes.RegistryAuthFromPreviousSpec:
   188  			if currentService.PreviousSpec == nil {
   189  				return nil, errors.New("service does not have a previous spec")
   190  			}
   191  			ctnr = currentService.PreviousSpec.Task.GetContainer()
   192  		default:
   193  			return nil, errors.New("unsupported registryAuthFrom value")
   194  		}
   195  		if ctnr == nil {
   196  			return nil, errors.New("service does not use container tasks")
   197  		}
   198  		newCtnr.PullOptions = ctnr.PullOptions
   199  		// update encodedAuth so it can be used to pin image by digest
   200  		if ctnr.PullOptions != nil {
   201  			encodedAuth = ctnr.PullOptions.RegistryAuth
   202  		}
   203  	}
   204  
   205  	// retrieve auth config from encoded auth
   206  	authConfig := &apitypes.AuthConfig{}
   207  	if encodedAuth != "" {
   208  		if err := json.NewDecoder(base64.NewDecoder(base64.URLEncoding, strings.NewReader(encodedAuth))).Decode(authConfig); err != nil {
   209  			logrus.Warnf("invalid authconfig: %v", err)
   210  		}
   211  	}
   212  
   213  	resp := &apitypes.ServiceUpdateResponse{}
   214  
   215  	// pin image by digest
   216  	if os.Getenv("DOCKER_SERVICE_PREFER_OFFLINE_IMAGE") != "1" {
   217  		digestImage, err := c.imageWithDigestString(ctx, newCtnr.Image, authConfig)
   218  		if err != nil {
   219  			logrus.Warnf("unable to pin image %s to digest: %s", newCtnr.Image, err.Error())
   220  			resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", newCtnr.Image, err.Error()))
   221  		} else if newCtnr.Image != digestImage {
   222  			logrus.Debugf("pinning image %s by digest: %s", newCtnr.Image, digestImage)
   223  			newCtnr.Image = digestImage
   224  		} else {
   225  			logrus.Debugf("updating service using supplied digest reference %s", newCtnr.Image)
   226  		}
   227  	}
   228  
   229  	_, err = state.controlClient.UpdateService(
   230  		ctx,
   231  		&swarmapi.UpdateServiceRequest{
   232  			ServiceID: currentService.ID,
   233  			Spec:      &serviceSpec,
   234  			ServiceVersion: &swarmapi.Version{
   235  				Index: version,
   236  			},
   237  		},
   238  	)
   239  
   240  	return resp, err
   241  }
   242  
   243  // RemoveService removes a service from a managed swarm cluster.
   244  func (c *Cluster) RemoveService(input string) error {
   245  	c.mu.RLock()
   246  	defer c.mu.RUnlock()
   247  
   248  	state := c.currentNodeState()
   249  	if !state.IsActiveManager() {
   250  		return c.errNoManager(state)
   251  	}
   252  
   253  	ctx, cancel := c.getRequestContext()
   254  	defer cancel()
   255  
   256  	service, err := getService(ctx, state.controlClient, input)
   257  	if err != nil {
   258  		return err
   259  	}
   260  
   261  	_, err = state.controlClient.RemoveService(ctx, &swarmapi.RemoveServiceRequest{ServiceID: service.ID})
   262  	return err
   263  }
   264  
   265  // ServiceLogs collects service logs and writes them back to `config.OutStream`
   266  func (c *Cluster) ServiceLogs(ctx context.Context, input string, config *backend.ContainerLogsConfig, started chan struct{}) error {
   267  	c.mu.RLock()
   268  	state := c.currentNodeState()
   269  	if !state.IsActiveManager() {
   270  		c.mu.RUnlock()
   271  		return c.errNoManager(state)
   272  	}
   273  
   274  	service, err := getService(ctx, state.controlClient, input)
   275  	if err != nil {
   276  		c.mu.RUnlock()
   277  		return err
   278  	}
   279  
   280  	stream, err := state.logsClient.SubscribeLogs(ctx, &swarmapi.SubscribeLogsRequest{
   281  		Selector: &swarmapi.LogSelector{
   282  			ServiceIDs: []string{service.ID},
   283  		},
   284  		Options: &swarmapi.LogSubscriptionOptions{
   285  			Follow: config.Follow,
   286  		},
   287  	})
   288  	if err != nil {
   289  		c.mu.RUnlock()
   290  		return err
   291  	}
   292  
   293  	wf := ioutils.NewWriteFlusher(config.OutStream)
   294  	defer wf.Close()
   295  	close(started)
   296  	wf.Flush()
   297  
   298  	outStream := stdcopy.NewStdWriter(wf, stdcopy.Stdout)
   299  	errStream := stdcopy.NewStdWriter(wf, stdcopy.Stderr)
   300  
   301  	// Release the lock before starting the stream.
   302  	c.mu.RUnlock()
   303  	for {
   304  		// Check the context before doing anything.
   305  		select {
   306  		case <-ctx.Done():
   307  			return ctx.Err()
   308  		default:
   309  		}
   310  
   311  		subscribeMsg, err := stream.Recv()
   312  		if err == io.EOF {
   313  			return nil
   314  		}
   315  		if err != nil {
   316  			return err
   317  		}
   318  
   319  		for _, msg := range subscribeMsg.Messages {
   320  			data := []byte{}
   321  
   322  			if config.Timestamps {
   323  				ts, err := gogotypes.TimestampFromProto(msg.Timestamp)
   324  				if err != nil {
   325  					return err
   326  				}
   327  				data = append(data, []byte(ts.Format(logger.TimeFormat)+" ")...)
   328  			}
   329  
   330  			data = append(data, []byte(fmt.Sprintf("%s.node.id=%s,%s.service.id=%s,%s.task.id=%s ",
   331  				contextPrefix, msg.Context.NodeID,
   332  				contextPrefix, msg.Context.ServiceID,
   333  				contextPrefix, msg.Context.TaskID,
   334  			))...)
   335  
   336  			data = append(data, msg.Data...)
   337  
   338  			switch msg.Stream {
   339  			case swarmapi.LogStreamStdout:
   340  				outStream.Write(data)
   341  			case swarmapi.LogStreamStderr:
   342  				errStream.Write(data)
   343  			}
   344  		}
   345  	}
   346  }
   347  
   348  // imageWithDigestString takes an image such as name or name:tag
   349  // and returns the image pinned to a digest, such as name@sha256:34234
   350  func (c *Cluster) imageWithDigestString(ctx context.Context, image string, authConfig *apitypes.AuthConfig) (string, error) {
   351  	ref, err := reference.ParseAnyReference(image)
   352  	if err != nil {
   353  		return "", err
   354  	}
   355  	namedRef, ok := ref.(reference.Named)
   356  	if !ok {
   357  		if _, ok := ref.(reference.Digested); ok {
   358  			return "", errors.New("image reference is an image ID")
   359  		}
   360  		return "", errors.Errorf("unknown image reference format: %s", image)
   361  	}
   362  	// only query registry if not a canonical reference (i.e. with digest)
   363  	if _, ok := namedRef.(reference.Canonical); !ok {
   364  		namedRef = reference.TagNameOnly(namedRef)
   365  
   366  		taggedRef, ok := namedRef.(reference.NamedTagged)
   367  		if !ok {
   368  			return "", errors.Errorf("image reference not tagged: %s", image)
   369  		}
   370  
   371  		repo, _, err := c.config.Backend.GetRepository(ctx, taggedRef, authConfig)
   372  		if err != nil {
   373  			return "", err
   374  		}
   375  		dscrptr, err := repo.Tags(ctx).Get(ctx, taggedRef.Tag())
   376  		if err != nil {
   377  			return "", err
   378  		}
   379  
   380  		namedDigestedRef, err := reference.WithDigest(taggedRef, dscrptr.Digest)
   381  		if err != nil {
   382  			return "", err
   383  		}
   384  		// return familiar form until interface updated to return type
   385  		return reference.FamiliarString(namedDigestedRef), nil
   386  	}
   387  	// reference already contains a digest, so just return it
   388  	return reference.FamiliarString(ref), nil
   389  }