7 "github.com/Sirupsen/logrus"
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 "golang.org/x/net/context"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/codes"
17 // MaxConfigSize is the maximum byte length of the `Config.Spec.Data` field.
18 const MaxConfigSize = 500 * 1024 // 500KB
20 // assumes spec is not nil
21 func configFromConfigSpec(spec *api.ConfigSpec) *api.Config {
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, grpc.Errorf(codes.InvalidArgument, "config ID must be provided")
38 var config *api.Config
39 s.store.View(func(tx store.ReadTx) {
40 config = store.GetConfig(tx, request.ConfigID)
44 return nil, grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
47 return &api.GetConfigResponse{Config: config}, nil
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, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
59 var config *api.Config
60 err := s.store.Update(func(tx store.Tx) error {
61 config = store.GetConfig(tx, request.ConfigID)
63 return grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
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 grpc.Errorf(codes.InvalidArgument, "only updates to Labels are allowed")
73 // We only allow updating Labels
74 config.Meta.Version = *request.ConfigVersion
75 config.Spec.Annotations.Labels = request.Spec.Annotations.Labels
77 return store.UpdateConfig(tx, config)
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")
89 return &api.UpdateConfigResponse{
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) {
101 configs []*api.Config
102 respConfigs []*api.Config
106 labels map[string]string
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))
114 for _, prefix := range request.Filters.NamePrefixes {
115 byFilters = append(byFilters, store.ByNamePrefix(prefix))
117 for _, prefix := range request.Filters.IDPrefixes {
118 byFilters = append(byFilters, store.ByIDPrefix(prefix))
120 labels = request.Filters.Labels
123 switch len(byFilters) {
129 by = store.Or(byFilters...)
132 s.store.View(func(tx store.ReadTx) {
133 configs, err = store.FindConfigs(tx, by)
140 for _, config := range configs {
141 if !filterMatchLabels(config.Spec.Annotations.Labels, labels) {
144 respConfigs = append(respConfigs, config)
147 return &api.ListConfigsResponse{Configs: respConfigs}, nil
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 {
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)
166 case store.ErrNameConflict:
167 return nil, grpc.Errorf(codes.AlreadyExists, "config %s already exists", request.Spec.Annotations.Name)
169 log.G(ctx).WithFields(logrus.Fields{
170 "config.Name": request.Spec.Annotations.Name,
171 "method": "CreateConfig",
172 }).Debugf("config created")
174 return &api.CreateConfigResponse{Config: config}, nil
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, grpc.Errorf(codes.InvalidArgument, "config ID must be provided")
190 err := s.store.Update(func(tx store.Tx) error {
191 // Check if the config exists
192 config := store.GetConfig(tx, request.ConfigID)
194 return grpc.Errorf(codes.NotFound, "could not find config %s", request.ConfigID)
197 // Check if any services currently reference this config, return error if so
198 services, err := store.FindServices(tx, store.ByReferencedConfigID(request.ConfigID))
200 return grpc.Errorf(codes.Internal, "could not find services using config %s: %v", request.ConfigID, err)
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)
209 configName := config.Spec.Annotations.Name
210 serviceNameStr := strings.Join(serviceNames, ", ")
211 serviceStr := "services"
212 if len(serviceNames) == 1 {
213 serviceStr = "service"
216 return grpc.Errorf(codes.InvalidArgument, "config '%s' is in use by the following %s: %v", configName, serviceStr, serviceNameStr)
219 return store.DeleteConfig(tx, request.ConfigID)
222 case store.ErrNotExist:
223 return nil, grpc.Errorf(codes.NotFound, "config %s not found", request.ConfigID)
225 log.G(ctx).WithFields(logrus.Fields{
226 "config.ID": request.ConfigID,
227 "method": "RemoveConfig",
228 }).Debugf("config removed")
230 return &api.RemoveConfigResponse{}, nil
236 func validateConfigSpec(spec *api.ConfigSpec) error {
238 return grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
240 if err := validateConfigOrSecretAnnotations(spec.Annotations); err != nil {
244 if len(spec.Data) >= MaxConfigSize || len(spec.Data) < 1 {
245 return grpc.Errorf(codes.InvalidArgument, "config data must be larger than 0 and less than %d bytes", MaxConfigSize)