github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/controlapi/config.go (about)

     1  package controlapi
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"strings"
     7  
     8  	"github.com/docker/swarmkit/api"
     9  	"github.com/docker/swarmkit/identity"
    10  	"github.com/docker/swarmkit/log"
    11  	"github.com/docker/swarmkit/manager/state/store"
    12  	"github.com/sirupsen/logrus"
    13  	"google.golang.org/grpc/codes"
    14  	"google.golang.org/grpc/status"
    15  )
    16  
    17  // MaxConfigSize is the maximum byte length of the `Config.Spec.Data` field.
    18  const MaxConfigSize = 1000 * 1024 // 500KB
    19  
    20  // assumes spec is not nil
    21  func configFromConfigSpec(spec *api.ConfigSpec) *api.Config {
    22  	return &api.Config{
    23  		ID:   identity.NewID(),
    24  		Spec: *spec,
    25  	}
    26  }
    27  
    28  // GetConfig returns a `GetConfigResponse` with a `Config` with the same
    29  // id as `GetConfigRequest.ConfigID`
    30  // - Returns `NotFound` if the Config with the given id is not found.
    31  // - Returns `InvalidArgument` if the `GetConfigRequest.ConfigID` is empty.
    32  // - Returns an error if getting fails.
    33  func (s *Server) GetConfig(ctx context.Context, request *api.GetConfigRequest) (*api.GetConfigResponse, error) {
    34  	if request.ConfigID == "" {
    35  		return nil, status.Errorf(codes.InvalidArgument, "config ID must be provided")
    36  	}
    37  
    38  	var config *api.Config
    39  	s.store.View(func(tx store.ReadTx) {
    40  		config = store.GetConfig(tx, request.ConfigID)
    41  	})
    42  
    43  	if config == nil {
    44  		return nil, status.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
    45  	}
    46  
    47  	return &api.GetConfigResponse{Config: config}, nil
    48  }
    49  
    50  // UpdateConfig updates a Config referenced by ConfigID with the given ConfigSpec.
    51  // - Returns `NotFound` if the Config is not found.
    52  // - Returns `InvalidArgument` if the ConfigSpec is malformed or anything other than Labels is changed
    53  // - Returns an error if the update fails.
    54  func (s *Server) UpdateConfig(ctx context.Context, request *api.UpdateConfigRequest) (*api.UpdateConfigResponse, error) {
    55  	if request.ConfigID == "" || request.ConfigVersion == nil {
    56  		return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
    57  	}
    58  
    59  	var config *api.Config
    60  	err := s.store.Update(func(tx store.Tx) error {
    61  		config = store.GetConfig(tx, request.ConfigID)
    62  		if config == nil {
    63  			return status.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
    64  		}
    65  
    66  		// Check if the Name is different than the current name, or the config is non-nil and different
    67  		// than the current config
    68  		if config.Spec.Annotations.Name != request.Spec.Annotations.Name ||
    69  			(request.Spec.Data != nil && !bytes.Equal(request.Spec.Data, config.Spec.Data)) {
    70  			return status.Errorf(codes.InvalidArgument, "only updates to Labels are allowed")
    71  		}
    72  
    73  		// We only allow updating Labels
    74  		config.Meta.Version = *request.ConfigVersion
    75  		config.Spec.Annotations.Labels = request.Spec.Annotations.Labels
    76  
    77  		return store.UpdateConfig(tx, config)
    78  	})
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	log.G(ctx).WithFields(logrus.Fields{
    84  		"config.ID":   request.ConfigID,
    85  		"config.Name": request.Spec.Annotations.Name,
    86  		"method":      "UpdateConfig",
    87  	}).Debugf("config updated")
    88  
    89  	return &api.UpdateConfigResponse{
    90  		Config: config,
    91  	}, nil
    92  }
    93  
    94  // ListConfigs returns a `ListConfigResponse` with a list all non-internal `Config`s being
    95  // managed, or all configs matching any name in `ListConfigsRequest.Names`, any
    96  // name prefix in `ListConfigsRequest.NamePrefixes`, any id in
    97  // `ListConfigsRequest.ConfigIDs`, or any id prefix in `ListConfigsRequest.IDPrefixes`.
    98  // - Returns an error if listing fails.
    99  func (s *Server) ListConfigs(ctx context.Context, request *api.ListConfigsRequest) (*api.ListConfigsResponse, error) {
   100  	var (
   101  		configs     []*api.Config
   102  		respConfigs []*api.Config
   103  		err         error
   104  		byFilters   []store.By
   105  		by          store.By
   106  		labels      map[string]string
   107  	)
   108  
   109  	// return all configs that match either any of the names or any of the name prefixes (why would you give both?)
   110  	if request.Filters != nil {
   111  		for _, name := range request.Filters.Names {
   112  			byFilters = append(byFilters, store.ByName(name))
   113  		}
   114  		for _, prefix := range request.Filters.NamePrefixes {
   115  			byFilters = append(byFilters, store.ByNamePrefix(prefix))
   116  		}
   117  		for _, prefix := range request.Filters.IDPrefixes {
   118  			byFilters = append(byFilters, store.ByIDPrefix(prefix))
   119  		}
   120  		labels = request.Filters.Labels
   121  	}
   122  
   123  	switch len(byFilters) {
   124  	case 0:
   125  		by = store.All
   126  	case 1:
   127  		by = byFilters[0]
   128  	default:
   129  		by = store.Or(byFilters...)
   130  	}
   131  
   132  	s.store.View(func(tx store.ReadTx) {
   133  		configs, err = store.FindConfigs(tx, by)
   134  	})
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	// filter by label
   140  	for _, config := range configs {
   141  		if !filterMatchLabels(config.Spec.Annotations.Labels, labels) {
   142  			continue
   143  		}
   144  		respConfigs = append(respConfigs, config)
   145  	}
   146  
   147  	return &api.ListConfigsResponse{Configs: respConfigs}, nil
   148  }
   149  
   150  // CreateConfig creates and returns a `CreateConfigResponse` with a `Config` based
   151  // on the provided `CreateConfigRequest.ConfigSpec`.
   152  // - Returns `InvalidArgument` if the `CreateConfigRequest.ConfigSpec` is malformed,
   153  //   or if the config data is too long or contains invalid characters.
   154  // - Returns an error if the creation fails.
   155  func (s *Server) CreateConfig(ctx context.Context, request *api.CreateConfigRequest) (*api.CreateConfigResponse, error) {
   156  	if err := validateConfigSpec(request.Spec); err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	config := configFromConfigSpec(request.Spec) // the store will handle name conflicts
   161  	err := s.store.Update(func(tx store.Tx) error {
   162  		return store.CreateConfig(tx, config)
   163  	})
   164  
   165  	switch err {
   166  	case store.ErrNameConflict:
   167  		return nil, status.Errorf(codes.AlreadyExists, "config %s already exists", request.Spec.Annotations.Name)
   168  	case nil:
   169  		log.G(ctx).WithFields(logrus.Fields{
   170  			"config.Name": request.Spec.Annotations.Name,
   171  			"method":      "CreateConfig",
   172  		}).Debugf("config created")
   173  
   174  		return &api.CreateConfigResponse{Config: config}, nil
   175  	default:
   176  		return nil, err
   177  	}
   178  }
   179  
   180  // RemoveConfig removes the config referenced by `RemoveConfigRequest.ID`.
   181  // - Returns `InvalidArgument` if `RemoveConfigRequest.ID` is empty.
   182  // - Returns `NotFound` if the a config named `RemoveConfigRequest.ID` is not found.
   183  // - Returns `ConfigInUse` if the config is currently in use
   184  // - Returns an error if the deletion fails.
   185  func (s *Server) RemoveConfig(ctx context.Context, request *api.RemoveConfigRequest) (*api.RemoveConfigResponse, error) {
   186  	if request.ConfigID == "" {
   187  		return nil, status.Errorf(codes.InvalidArgument, "config ID must be provided")
   188  	}
   189  
   190  	err := s.store.Update(func(tx store.Tx) error {
   191  		// Check if the config exists
   192  		config := store.GetConfig(tx, request.ConfigID)
   193  		if config == nil {
   194  			return status.Errorf(codes.NotFound, "could not find config %s", request.ConfigID)
   195  		}
   196  
   197  		// Check if any services currently reference this config, return error if so
   198  		services, err := store.FindServices(tx, store.ByReferencedConfigID(request.ConfigID))
   199  		if err != nil {
   200  			return status.Errorf(codes.Internal, "could not find services using config %s: %v", request.ConfigID, err)
   201  		}
   202  
   203  		if len(services) != 0 {
   204  			serviceNames := make([]string, 0, len(services))
   205  			for _, service := range services {
   206  				serviceNames = append(serviceNames, service.Spec.Annotations.Name)
   207  			}
   208  
   209  			configName := config.Spec.Annotations.Name
   210  			serviceNameStr := strings.Join(serviceNames, ", ")
   211  			serviceStr := "services"
   212  			if len(serviceNames) == 1 {
   213  				serviceStr = "service"
   214  			}
   215  
   216  			return status.Errorf(codes.InvalidArgument, "config '%s' is in use by the following %s: %v", configName, serviceStr, serviceNameStr)
   217  		}
   218  
   219  		return store.DeleteConfig(tx, request.ConfigID)
   220  	})
   221  	switch err {
   222  	case store.ErrNotExist:
   223  		return nil, status.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
   224  	case nil:
   225  		log.G(ctx).WithFields(logrus.Fields{
   226  			"config.ID": request.ConfigID,
   227  			"method":    "RemoveConfig",
   228  		}).Debugf("config removed")
   229  
   230  		return &api.RemoveConfigResponse{}, nil
   231  	default:
   232  		return nil, err
   233  	}
   234  }
   235  
   236  func validateConfigSpec(spec *api.ConfigSpec) error {
   237  	if spec == nil {
   238  		return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
   239  	}
   240  	if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil {
   241  		return err
   242  	}
   243  
   244  	if len(spec.Data) >= MaxConfigSize || len(spec.Data) < 1 {
   245  		return status.Errorf(codes.InvalidArgument, "config data must be larger than 0 and less than %d bytes", MaxConfigSize)
   246  	}
   247  	return nil
   248  }