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 }