123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- package controlapi
- import (
- "context"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/status"
- "github.com/moby/swarmkit/v2/api"
- "github.com/moby/swarmkit/v2/identity"
- "github.com/moby/swarmkit/v2/log"
- "github.com/moby/swarmkit/v2/manager/state/store"
- )
- // CreateResource returns a `CreateResourceResponse` after creating a `Resource` based
- // on the provided `CreateResourceRequest.Resource`.
- // - Returns `InvalidArgument` if the `CreateResourceRequest.Resource` is malformed,
- // or if the config data is too long or contains invalid characters.
- // - Returns an error if the creation fails.
- func (s *Server) CreateResource(ctx context.Context, request *api.CreateResourceRequest) (*api.CreateResourceResponse, error) {
- if request.Annotations == nil || request.Annotations.Name == "" {
- return nil, status.Errorf(codes.InvalidArgument, "Resource must have a name")
- }
- // finally, validate that Kind is not an emptystring. We know that creating
- // with Kind as empty string should fail at the store level, but to make
- // errors clearer, special case this.
- if request.Kind == "" {
- return nil, status.Errorf(codes.InvalidArgument, "Resource must belong to an Extension")
- }
- r := &api.Resource{
- ID: identity.NewID(),
- Annotations: *request.Annotations,
- Kind: request.Kind,
- Payload: request.Payload,
- }
- err := s.store.Update(func(tx store.Tx) error {
- return store.CreateResource(tx, r)
- })
- switch err {
- case store.ErrNoKind:
- return nil, status.Errorf(codes.InvalidArgument, "Kind %v is not registered", r.Kind)
- case store.ErrNameConflict:
- return nil, status.Errorf(
- codes.AlreadyExists,
- "A resource with name %v already exists",
- r.Annotations.Name,
- )
- case nil:
- log.G(ctx).WithFields(log.Fields{
- "resource.Name": r.Annotations.Name,
- "method": "CreateResource",
- }).Debugf("resource created")
- return &api.CreateResourceResponse{Resource: r}, nil
- default:
- return nil, err
- }
- }
- // GetResource returns a `GetResourceResponse` with a `Resource` with the same
- // id as `GetResourceRequest.Resource`
- // - Returns `NotFound` if the Resource with the given id is not found.
- // - Returns `InvalidArgument` if the `GetResourceRequest.Resource` is empty.
- // - Returns an error if getting fails.
- func (s *Server) GetResource(ctx context.Context, request *api.GetResourceRequest) (*api.GetResourceResponse, error) {
- if request.ResourceID == "" {
- return nil, status.Errorf(codes.InvalidArgument, "resource ID must be present")
- }
- var resource *api.Resource
- s.store.View(func(tx store.ReadTx) {
- resource = store.GetResource(tx, request.ResourceID)
- })
- if resource == nil {
- return nil, status.Errorf(codes.NotFound, "resource %s not found", request.ResourceID)
- }
- return &api.GetResourceResponse{Resource: resource}, nil
- }
- // RemoveResource removes the `Resource` referenced by `RemoveResourceRequest.ResourceID`.
- // - Returns `InvalidArgument` if `RemoveResourceRequest.ResourceID` is empty.
- // - Returns `NotFound` if the a resource named `RemoveResourceRequest.ResourceID` is not found.
- // - Returns an error if the deletion fails.
- func (s *Server) RemoveResource(ctx context.Context, request *api.RemoveResourceRequest) (*api.RemoveResourceResponse, error) {
- if request.ResourceID == "" {
- return nil, status.Errorf(codes.InvalidArgument, "resource ID must be present")
- }
- err := s.store.Update(func(tx store.Tx) error {
- return store.DeleteResource(tx, request.ResourceID)
- })
- switch err {
- case store.ErrNotExist:
- return nil, status.Errorf(codes.NotFound, "resource %s not found", request.ResourceID)
- case nil:
- return &api.RemoveResourceResponse{}, nil
- default:
- return nil, err
- }
- }
- // ListResources returns a `ListResourcesResponse` with a list of `Resource`s stored in the raft store,
- // or all resources matching any name in `ListConfigsRequest.Names`, any
- // name prefix in `ListResourcesRequest.NamePrefixes`, any id in
- // `ListResourcesRequest.ResourceIDs`, or any id prefix in `ListResourcesRequest.IDPrefixes`.
- // - Returns an error if listing fails.
- func (s *Server) ListResources(ctx context.Context, request *api.ListResourcesRequest) (*api.ListResourcesResponse, error) {
- var (
- resources []*api.Resource
- respResources []*api.Resource
- err error
- byFilters []store.By
- by store.By
- labels map[string]string
- )
- // andKind is set to true if the Extension filter is not the only filter
- // being used. If this is the case, we do not have to compare by strings,
- // which could be slow.
- var andKind bool
- if request.Filters != nil {
- for _, name := range request.Filters.Names {
- byFilters = append(byFilters, store.ByName(name))
- }
- for _, prefix := range request.Filters.NamePrefixes {
- byFilters = append(byFilters, store.ByNamePrefix(prefix))
- }
- for _, prefix := range request.Filters.IDPrefixes {
- byFilters = append(byFilters, store.ByIDPrefix(prefix))
- }
- labels = request.Filters.Labels
- if request.Filters.Kind != "" {
- // if we're filtering on Extensions, then set this to true. If Kind is
- // the _only_ kind of filter, we'll set this to false below.
- andKind = true
- }
- }
- switch len(byFilters) {
- case 0:
- // NOTE(dperny): currently, filtering using store.ByKind would apply an
- // Or operation, which means that filtering by kind would return a
- // union. However, for Kind filters, we actually want the
- // _intersection_; that is, _only_ objects of the specified kind. we
- // could dig into the db code to figure out how to write and use an
- // efficient And combinator, but I don't have the time nor expertise to
- // do so at the moment. instead, we'll filter by kind after the fact.
- // however, if there are no other kinds of filters, and we're ONLY
- // listing by Kind, we can set that to be the only filter.
- if andKind {
- by = store.ByKind(request.Filters.Kind)
- andKind = false
- } else {
- by = store.All
- }
- case 1:
- by = byFilters[0]
- default:
- by = store.Or(byFilters...)
- }
- s.store.View(func(tx store.ReadTx) {
- resources, err = store.FindResources(tx, by)
- })
- if err != nil {
- return nil, err
- }
- // filter by label and extension
- for _, resource := range resources {
- if !filterMatchLabels(resource.Annotations.Labels, labels) {
- continue
- }
- if andKind && resource.Kind != request.Filters.Kind {
- continue
- }
- respResources = append(respResources, resource)
- }
- return &api.ListResourcesResponse{Resources: respResources}, nil
- }
- // UpdateResource updates the resource with the given `UpdateResourceRequest.Resource.Id` using the given `UpdateResourceRequest.Resource` and returns a `UpdateResourceResponse`.
- // - Returns `NotFound` if the Resource with the given `UpdateResourceRequest.Resource.Id` is not found.
- // - Returns `InvalidArgument` if the UpdateResourceRequest.Resource.Id` is empty.
- // - Returns an error if updating fails.
- func (s *Server) UpdateResource(ctx context.Context, request *api.UpdateResourceRequest) (*api.UpdateResourceResponse, error) {
- if request.ResourceID == "" || request.ResourceVersion == nil {
- return nil, status.Errorf(codes.InvalidArgument, "must include ID and version")
- }
- var r *api.Resource
- err := s.store.Update(func(tx store.Tx) error {
- r = store.GetResource(tx, request.ResourceID)
- if r == nil {
- return status.Errorf(codes.NotFound, "resource %v not found", request.ResourceID)
- }
- if request.Annotations != nil {
- if r.Annotations.Name != request.Annotations.Name {
- return status.Errorf(codes.InvalidArgument, "cannot change resource name")
- }
- r.Annotations = *request.Annotations
- }
- r.Meta.Version = *request.ResourceVersion
- // only alter the payload if the
- if request.Payload != nil {
- r.Payload = request.Payload
- }
- return store.UpdateResource(tx, r)
- })
- switch err {
- case store.ErrSequenceConflict:
- return nil, status.Errorf(codes.InvalidArgument, "update out of sequence")
- case nil:
- return &api.UpdateResourceResponse{
- Resource: r,
- }, nil
- default:
- return nil, err
- }
- }
|