123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package controlapi
- import (
- "bytes"
- "strings"
- "github.com/Sirupsen/logrus"
- "github.com/docker/swarmkit/api"
- "github.com/docker/swarmkit/identity"
- "github.com/docker/swarmkit/log"
- "github.com/docker/swarmkit/manager/state/store"
- "golang.org/x/net/context"
- "google.golang.org/grpc"
- "google.golang.org/grpc/codes"
- )
- // MaxConfigSize is the maximum byte length of the `Config.Spec.Data` field.
- const MaxConfigSize = 500 * 1024 // 500KB
- // assumes spec is not nil
- func configFromConfigSpec(spec *api.ConfigSpec) *api.Config {
- return &api.Config{
- ID: identity.NewID(),
- Spec: *spec,
- }
- }
- // GetConfig returns a `GetConfigResponse` with a `Config` with the same
- // id as `GetConfigRequest.ConfigID`
- // - Returns `NotFound` if the Config with the given id is not found.
- // - Returns `InvalidArgument` if the `GetConfigRequest.ConfigID` is empty.
- // - Returns an error if getting fails.
- func (s *Server) GetConfig(ctx context.Context, request *api.GetConfigRequest) (*api.GetConfigResponse, error) {
- if request.ConfigID == "" {
- return nil, grpc.Errorf(codes.InvalidArgument, "config ID must be provided")
- }
- var config *api.Config
- s.store.View(func(tx store.ReadTx) {
- config = store.GetConfig(tx, request.ConfigID)
- })
- if config == nil {
- return nil, grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
- }
- return &api.GetConfigResponse{Config: config}, nil
- }
- // UpdateConfig updates a Config referenced by ConfigID with the given ConfigSpec.
- // - Returns `NotFound` if the Config is not found.
- // - Returns `InvalidArgument` if the ConfigSpec is malformed or anything other than Labels is changed
- // - Returns an error if the update fails.
- func (s *Server) UpdateConfig(ctx context.Context, request *api.UpdateConfigRequest) (*api.UpdateConfigResponse, error) {
- if request.ConfigID == "" || request.ConfigVersion == nil {
- return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
- }
- var config *api.Config
- err := s.store.Update(func(tx store.Tx) error {
- config = store.GetConfig(tx, request.ConfigID)
- if config == nil {
- return grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
- }
- // Check if the Name is different than the current name, or the config is non-nil and different
- // than the current config
- if config.Spec.Annotations.Name != request.Spec.Annotations.Name ||
- (request.Spec.Data != nil && !bytes.Equal(request.Spec.Data, config.Spec.Data)) {
- return grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed")
- }
- // We only allow updating Labels
- config.Meta.Version = *request.ConfigVersion
- config.Spec.Annotations.Labels = request.Spec.Annotations.Labels
- return store.UpdateConfig(tx, config)
- })
- if err != nil {
- return nil, err
- }
- log.G(ctx).WithFields(logrus.Fields{
- "config.ID": request.ConfigID,
- "config.Name": request.Spec.Annotations.Name,
- "method": "UpdateConfig",
- }).Debugf("config updated")
- return &api.UpdateConfigResponse{
- Config: config,
- }, nil
- }
- // ListConfigs returns a `ListConfigResponse` with a list all non-internal `Config`s being
- // managed, or all configs matching any name in `ListConfigsRequest.Names`, any
- // name prefix in `ListConfigsRequest.NamePrefixes`, any id in
- // `ListConfigsRequest.ConfigIDs`, or any id prefix in `ListConfigsRequest.IDPrefixes`.
- // - Returns an error if listing fails.
- func (s *Server) ListConfigs(ctx context.Context, request *api.ListConfigsRequest) (*api.ListConfigsResponse, error) {
- var (
- configs []*api.Config
- respConfigs []*api.Config
- err error
- byFilters []store.By
- by store.By
- labels map[string]string
- )
- // return all configs that match either any of the names or any of the name prefixes (why would you give both?)
- if request.Filters != nil {
- for _, name := range request.Filters.Names {
- byFilters = append(byFilters, store.ByName(name))
- }
- for _, prefix := range request.Filters.NamePrefixes {
- byFilters = append(byFilters, store.ByNamePrefix(prefix))
- }
- for _, prefix := range request.Filters.IDPrefixes {
- byFilters = append(byFilters, store.ByIDPrefix(prefix))
- }
- labels = request.Filters.Labels
- }
- switch len(byFilters) {
- case 0:
- by = store.All
- case 1:
- by = byFilters[0]
- default:
- by = store.Or(byFilters...)
- }
- s.store.View(func(tx store.ReadTx) {
- configs, err = store.FindConfigs(tx, by)
- })
- if err != nil {
- return nil, err
- }
- // filter by label
- for _, config := range configs {
- if !filterMatchLabels(config.Spec.Annotations.Labels, labels) {
- continue
- }
- respConfigs = append(respConfigs, config)
- }
- return &api.ListConfigsResponse{Configs: respConfigs}, nil
- }
- // CreateConfig creates and returns a `CreateConfigResponse` with a `Config` based
- // on the provided `CreateConfigRequest.ConfigSpec`.
- // - Returns `InvalidArgument` if the `CreateConfigRequest.ConfigSpec` is malformed,
- // or if the config data is too long or contains invalid characters.
- // - Returns an error if the creation fails.
- func (s *Server) CreateConfig(ctx context.Context, request *api.CreateConfigRequest) (*api.CreateConfigResponse, error) {
- if err := validateConfigSpec(request.Spec); err != nil {
- return nil, err
- }
- config := configFromConfigSpec(request.Spec) // the store will handle name conflicts
- err := s.store.Update(func(tx store.Tx) error {
- return store.CreateConfig(tx, config)
- })
- switch err {
- case store.ErrNameConflict:
- return nil, grpc.Errorf(codes.AlreadyExists, "config %s already exists", request.Spec.Annotations.Name)
- case nil:
- log.G(ctx).WithFields(logrus.Fields{
- "config.Name": request.Spec.Annotations.Name,
- "method": "CreateConfig",
- }).Debugf("config created")
- return &api.CreateConfigResponse{Config: config}, nil
- default:
- return nil, err
- }
- }
- // RemoveConfig removes the config referenced by `RemoveConfigRequest.ID`.
- // - Returns `InvalidArgument` if `RemoveConfigRequest.ID` is empty.
- // - Returns `NotFound` if the a config named `RemoveConfigRequest.ID` is not found.
- // - Returns `ConfigInUse` if the config is currently in use
- // - Returns an error if the deletion fails.
- func (s *Server) RemoveConfig(ctx context.Context, request *api.RemoveConfigRequest) (*api.RemoveConfigResponse, error) {
- if request.ConfigID == "" {
- return nil, grpc.Errorf(codes.InvalidArgument, "config ID must be provided")
- }
- err := s.store.Update(func(tx store.Tx) error {
- // Check if the config exists
- config := store.GetConfig(tx, request.ConfigID)
- if config == nil {
- return grpc.Errorf(codes.NotFound, "could not find config %s", request.ConfigID)
- }
- // Check if any services currently reference this config, return error if so
- services, err := store.FindServices(tx, store.ByReferencedConfigID(request.ConfigID))
- if err != nil {
- return grpc.Errorf(codes.Internal, "could not find services using config %s: %v", request.ConfigID, err)
- }
- if len(services) != 0 {
- serviceNames := make([]string, 0, len(services))
- for _, service := range services {
- serviceNames = append(serviceNames, service.Spec.Annotations.Name)
- }
- configName := config.Spec.Annotations.Name
- serviceNameStr := strings.Join(serviceNames, ", ")
- serviceStr := "services"
- if len(serviceNames) == 1 {
- serviceStr = "service"
- }
- return grpc.Errorf(codes.InvalidArgument, "config '%s' is in use by the following %s: %v", configName, serviceStr, serviceNameStr)
- }
- return store.DeleteConfig(tx, request.ConfigID)
- })
- switch err {
- case store.ErrNotExist:
- return nil, grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
- case nil:
- log.G(ctx).WithFields(logrus.Fields{
- "config.ID": request.ConfigID,
- "method": "RemoveConfig",
- }).Debugf("config removed")
- return &api.RemoveConfigResponse{}, nil
- default:
- return nil, err
- }
- }
- func validateConfigSpec(spec *api.ConfigSpec) error {
- if spec == nil {
- return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
- }
- if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil {
- return err
- }
- if len(spec.Data) >= MaxConfigSize || len(spec.Data) < 1 {
- return grpc.Errorf(codes.InvalidArgument, "config data must be larger than 0 and less than %d bytes", MaxConfigSize)
- }
- return nil
- }
|