github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/controlapi/resource.go (about) 1 package controlapi 2 3 import ( 4 "context" 5 6 "github.com/sirupsen/logrus" 7 "google.golang.org/grpc/codes" 8 "google.golang.org/grpc/status" 9 10 "github.com/docker/swarmkit/api" 11 "github.com/docker/swarmkit/identity" 12 "github.com/docker/swarmkit/log" 13 "github.com/docker/swarmkit/manager/state/store" 14 ) 15 16 // CreateResource returns a `CreateResourceResponse` after creating a `Resource` based 17 // on the provided `CreateResourceRequest.Resource`. 18 // - Returns `InvalidArgument` if the `CreateResourceRequest.Resource` is malformed, 19 // or if the config data is too long or contains invalid characters. 20 // - Returns an error if the creation fails. 21 func (s *Server) CreateResource(ctx context.Context, request *api.CreateResourceRequest) (*api.CreateResourceResponse, error) { 22 if request.Annotations == nil || request.Annotations.Name == "" { 23 return nil, status.Errorf(codes.InvalidArgument, "Resource must have a name") 24 } 25 26 // finally, validate that Kind is not an emptystring. We know that creating 27 // with Kind as empty string should fail at the store level, but to make 28 // errors clearer, special case this. 29 if request.Kind == "" { 30 return nil, status.Errorf(codes.InvalidArgument, "Resource must belong to an Extension") 31 } 32 r := &api.Resource{ 33 ID: identity.NewID(), 34 Annotations: *request.Annotations, 35 Kind: request.Kind, 36 Payload: request.Payload, 37 } 38 39 err := s.store.Update(func(tx store.Tx) error { 40 return store.CreateResource(tx, r) 41 }) 42 43 switch err { 44 case store.ErrNoKind: 45 return nil, status.Errorf(codes.InvalidArgument, "Kind %v is not registered", r.Kind) 46 case store.ErrNameConflict: 47 return nil, status.Errorf( 48 codes.AlreadyExists, 49 "A resource with name %v already exists", 50 r.Annotations.Name, 51 ) 52 case nil: 53 log.G(ctx).WithFields(logrus.Fields{ 54 "resource.Name": r.Annotations.Name, 55 "method": "CreateResource", 56 }).Debugf("resource created") 57 return &api.CreateResourceResponse{Resource: r}, nil 58 default: 59 return nil, err 60 } 61 } 62 63 // GetResource returns a `GetResourceResponse` with a `Resource` with the same 64 // id as `GetResourceRequest.Resource` 65 // - Returns `NotFound` if the Resource with the given id is not found. 66 // - Returns `InvalidArgument` if the `GetResourceRequest.Resource` is empty. 67 // - Returns an error if getting fails. 68 func (s *Server) GetResource(ctx context.Context, request *api.GetResourceRequest) (*api.GetResourceResponse, error) { 69 if request.ResourceID == "" { 70 return nil, status.Errorf(codes.InvalidArgument, "resource ID must be present") 71 } 72 var resource *api.Resource 73 s.store.View(func(tx store.ReadTx) { 74 resource = store.GetResource(tx, request.ResourceID) 75 }) 76 77 if resource == nil { 78 return nil, status.Errorf(codes.NotFound, "resource %s not found", request.ResourceID) 79 } 80 81 return &api.GetResourceResponse{Resource: resource}, nil 82 } 83 84 // RemoveResource removes the `Resource` referenced by `RemoveResourceRequest.ResourceID`. 85 // - Returns `InvalidArgument` if `RemoveResourceRequest.ResourceID` is empty. 86 // - Returns `NotFound` if the a resource named `RemoveResourceRequest.ResourceID` is not found. 87 // - Returns an error if the deletion fails. 88 func (s *Server) RemoveResource(ctx context.Context, request *api.RemoveResourceRequest) (*api.RemoveResourceResponse, error) { 89 if request.ResourceID == "" { 90 return nil, status.Errorf(codes.InvalidArgument, "resource ID must be present") 91 } 92 err := s.store.Update(func(tx store.Tx) error { 93 return store.DeleteResource(tx, request.ResourceID) 94 }) 95 switch err { 96 case store.ErrNotExist: 97 return nil, status.Errorf(codes.NotFound, "resource %s not found", request.ResourceID) 98 case nil: 99 return &api.RemoveResourceResponse{}, nil 100 default: 101 return nil, err 102 } 103 } 104 105 // ListResources returns a `ListResourcesResponse` with a list of `Resource`s stored in the raft store, 106 // or all resources matching any name in `ListConfigsRequest.Names`, any 107 // name prefix in `ListResourcesRequest.NamePrefixes`, any id in 108 // `ListResourcesRequest.ResourceIDs`, or any id prefix in `ListResourcesRequest.IDPrefixes`. 109 // - Returns an error if listing fails. 110 func (s *Server) ListResources(ctx context.Context, request *api.ListResourcesRequest) (*api.ListResourcesResponse, error) { 111 var ( 112 resources []*api.Resource 113 respResources []*api.Resource 114 err error 115 byFilters []store.By 116 by store.By 117 labels map[string]string 118 ) 119 120 // andKind is set to true if the Extension filter is not the only filter 121 // being used. If this is the case, we do not have to compare by strings, 122 // which could be slow. 123 var andKind bool 124 125 if request.Filters != nil { 126 for _, name := range request.Filters.Names { 127 byFilters = append(byFilters, store.ByName(name)) 128 } 129 for _, prefix := range request.Filters.NamePrefixes { 130 byFilters = append(byFilters, store.ByNamePrefix(prefix)) 131 } 132 for _, prefix := range request.Filters.IDPrefixes { 133 byFilters = append(byFilters, store.ByIDPrefix(prefix)) 134 } 135 labels = request.Filters.Labels 136 if request.Filters.Kind != "" { 137 // if we're filtering on Extensions, then set this to true. If Kind is 138 // the _only_ kind of filter, we'll set this to false below. 139 andKind = true 140 } 141 } 142 143 switch len(byFilters) { 144 case 0: 145 // NOTE(dperny): currently, filtering using store.ByKind would apply an 146 // Or operation, which means that filtering by kind would return a 147 // union. However, for Kind filters, we actually want the 148 // _intersection_; that is, _only_ objects of the specified kind. we 149 // could dig into the db code to figure out how to write and use an 150 // efficient And combinator, but I don't have the time nor expertise to 151 // do so at the moment. instead, we'll filter by kind after the fact. 152 // however, if there are no other kinds of filters, and we're ONLY 153 // listing by Kind, we can set that to be the only filter. 154 if andKind { 155 by = store.ByKind(request.Filters.Kind) 156 andKind = false 157 } else { 158 by = store.All 159 } 160 case 1: 161 by = byFilters[0] 162 default: 163 by = store.Or(byFilters...) 164 } 165 166 s.store.View(func(tx store.ReadTx) { 167 resources, err = store.FindResources(tx, by) 168 }) 169 if err != nil { 170 return nil, err 171 } 172 173 // filter by label and extension 174 for _, resource := range resources { 175 if !filterMatchLabels(resource.Annotations.Labels, labels) { 176 continue 177 } 178 if andKind && resource.Kind != request.Filters.Kind { 179 continue 180 } 181 respResources = append(respResources, resource) 182 } 183 184 return &api.ListResourcesResponse{Resources: respResources}, nil 185 } 186 187 // UpdateResource updates the resource with the given `UpdateResourceRequest.Resource.Id` using the given `UpdateResourceRequest.Resource` and returns a `UpdateResourceResponse`. 188 // - Returns `NotFound` if the Resource with the given `UpdateResourceRequest.Resource.Id` is not found. 189 // - Returns `InvalidArgument` if the UpdateResourceRequest.Resource.Id` is empty. 190 // - Returns an error if updating fails. 191 func (s *Server) UpdateResource(ctx context.Context, request *api.UpdateResourceRequest) (*api.UpdateResourceResponse, error) { 192 if request.ResourceID == "" || request.ResourceVersion == nil { 193 return nil, status.Errorf(codes.InvalidArgument, "must include ID and version") 194 } 195 var r *api.Resource 196 err := s.store.Update(func(tx store.Tx) error { 197 r = store.GetResource(tx, request.ResourceID) 198 if r == nil { 199 return status.Errorf(codes.NotFound, "resource %v not found", request.ResourceID) 200 } 201 202 if request.Annotations != nil { 203 if r.Annotations.Name != request.Annotations.Name { 204 return status.Errorf(codes.InvalidArgument, "cannot change resource name") 205 } 206 r.Annotations = *request.Annotations 207 } 208 r.Meta.Version = *request.ResourceVersion 209 // only alter the payload if the 210 if request.Payload != nil { 211 r.Payload = request.Payload 212 } 213 214 return store.UpdateResource(tx, r) 215 }) 216 switch err { 217 case store.ErrSequenceConflict: 218 return nil, status.Errorf(codes.InvalidArgument, "update out of sequence") 219 case nil: 220 return &api.UpdateResourceResponse{ 221 Resource: r, 222 }, nil 223 default: 224 return nil, err 225 } 226 }