resource.go 7.8 KB

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