227 lines
7.9 KiB
Go
227 lines
7.9 KiB
Go
package controlapi
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/docker/swarmkit/api"
|
|
"github.com/docker/swarmkit/identity"
|
|
"github.com/docker/swarmkit/log"
|
|
"github.com/docker/swarmkit/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(logrus.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
|
|
}
|
|
}
|