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  }