mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Most of the code in the filters package relates to the unmarshaling, validation and application of filters from client requests. None of this is necessary or particularly useful for Go SDK users. Move the full-fat filters package into daemon/internal and switch all the daemon code to import that package so we are free to iterate upon the code without worrying about source-code interface compatibility. Signed-off-by: Cory Snider <csnider@mirantis.com>
552 lines
17 KiB
Go
552 lines
17 KiB
Go
package swarm
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/moby/moby/api/types/registry"
|
|
types "github.com/moby/moby/api/types/swarm"
|
|
"github.com/moby/moby/api/types/versions"
|
|
"github.com/moby/moby/v2/daemon/internal/filters"
|
|
"github.com/moby/moby/v2/daemon/server/backend"
|
|
"github.com/moby/moby/v2/daemon/server/httputils"
|
|
"github.com/moby/moby/v2/daemon/server/swarmbackend"
|
|
"github.com/moby/moby/v2/errdefs"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func (sr *swarmRouter) initCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var req types.InitRequest
|
|
if err := httputils.ReadJSON(r, &req); err != nil {
|
|
return err
|
|
}
|
|
version := httputils.VersionFromContext(ctx)
|
|
|
|
// DefaultAddrPool and SubnetSize were added in API 1.39. Ignore on older API versions.
|
|
if versions.LessThan(version, "1.39") {
|
|
req.DefaultAddrPool = nil
|
|
req.SubnetSize = 0
|
|
}
|
|
// DataPathPort was added in API 1.40. Ignore this option on older API versions.
|
|
if versions.LessThan(version, "1.40") {
|
|
req.DataPathPort = 0
|
|
}
|
|
nodeID, err := sr.backend.Init(req)
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error initializing swarm")
|
|
return err
|
|
}
|
|
return httputils.WriteJSON(w, http.StatusOK, nodeID)
|
|
}
|
|
|
|
func (sr *swarmRouter) joinCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var req types.JoinRequest
|
|
if err := httputils.ReadJSON(r, &req); err != nil {
|
|
return err
|
|
}
|
|
return sr.backend.Join(req)
|
|
}
|
|
|
|
func (sr *swarmRouter) leaveCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
force := httputils.BoolValue(r, "force")
|
|
return sr.backend.Leave(ctx, force)
|
|
}
|
|
|
|
func (sr *swarmRouter) inspectCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
swarm, err := sr.backend.Inspect()
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error getting swarm")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, swarm)
|
|
}
|
|
|
|
func (sr *swarmRouter) updateCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var swarm types.Spec
|
|
if err := httputils.ReadJSON(r, &swarm); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
err := fmt.Errorf("invalid swarm version '%s': %v", rawVersion, err)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
var flags swarmbackend.UpdateFlags
|
|
|
|
if value := r.URL.Query().Get("rotateWorkerToken"); value != "" {
|
|
rot, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
err := fmt.Errorf("invalid value for rotateWorkerToken: %s", value)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
flags.RotateWorkerToken = rot
|
|
}
|
|
|
|
if value := r.URL.Query().Get("rotateManagerToken"); value != "" {
|
|
rot, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
err := fmt.Errorf("invalid value for rotateManagerToken: %s", value)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
flags.RotateManagerToken = rot
|
|
}
|
|
|
|
if value := r.URL.Query().Get("rotateManagerUnlockKey"); value != "" {
|
|
rot, err := strconv.ParseBool(value)
|
|
if err != nil {
|
|
return errdefs.InvalidParameter(fmt.Errorf("invalid value for rotateManagerUnlockKey: %s", value))
|
|
}
|
|
|
|
flags.RotateManagerUnlockKey = rot
|
|
}
|
|
|
|
if err := sr.backend.Update(version, swarm, flags); err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error configuring swarm")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) unlockCluster(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var req types.UnlockRequest
|
|
if err := httputils.ReadJSON(r, &req); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := sr.backend.UnlockSwarm(req); err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error unlocking swarm")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) getUnlockKey(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
unlockKey, err := sr.backend.GetUnlockKey()
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error retrieving swarm unlock key")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, &types.UnlockKeyResponse{
|
|
UnlockKey: unlockKey,
|
|
})
|
|
}
|
|
|
|
func (sr *swarmRouter) getServices(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
filter, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// the status query parameter is only support in API versions >= 1.41. If
|
|
// the client is using a lesser version, ignore the parameter.
|
|
cliVersion := httputils.VersionFromContext(ctx)
|
|
var status bool
|
|
if value := r.URL.Query().Get("status"); value != "" && !versions.LessThan(cliVersion, "1.41") {
|
|
var err error
|
|
status, err = strconv.ParseBool(value)
|
|
if err != nil {
|
|
return errors.Wrapf(errdefs.InvalidParameter(err), "invalid value for status: %s", value)
|
|
}
|
|
}
|
|
|
|
services, err := sr.backend.GetServices(swarmbackend.ServiceListOptions{Filters: filter, Status: status})
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error getting services")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, services)
|
|
}
|
|
|
|
func (sr *swarmRouter) getService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var insertDefaults bool
|
|
|
|
if value := r.URL.Query().Get("insertDefaults"); value != "" {
|
|
var err error
|
|
insertDefaults, err = strconv.ParseBool(value)
|
|
if err != nil {
|
|
return errors.Wrapf(errdefs.InvalidParameter(err), "invalid value for insertDefaults: %s", value)
|
|
}
|
|
}
|
|
|
|
// you may note that there is no code here to handle the "status" query
|
|
// parameter, as in getServices. the Status field is not supported when
|
|
// retrieving an individual service because the Backend API changes
|
|
// required to accommodate it would be too disruptive, and because that
|
|
// field is so rarely needed as part of an individual service inspection.
|
|
|
|
service, err := sr.backend.GetService(vars["id"], insertDefaults)
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"service-id": vars["id"],
|
|
}).Debug("Error getting service")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, service)
|
|
}
|
|
|
|
func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var service types.ServiceSpec
|
|
if err := httputils.ReadJSON(r, &service); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get returns "" if the header does not exist
|
|
encodedAuth := r.Header.Get(registry.AuthHeader)
|
|
queryRegistry := false
|
|
if v := httputils.VersionFromContext(ctx); v != "" {
|
|
if versions.LessThan(v, "1.30") {
|
|
queryRegistry = true
|
|
}
|
|
adjustForAPIVersion(v, &service)
|
|
}
|
|
|
|
resp, err := sr.backend.CreateService(service, encodedAuth, queryRegistry)
|
|
if err != nil {
|
|
log.G(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"service-name": service.Name,
|
|
}).Debug("Error creating service")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusCreated, resp)
|
|
}
|
|
|
|
func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var service types.ServiceSpec
|
|
if err := httputils.ReadJSON(r, &service); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
err := fmt.Errorf("invalid service version '%s': %v", rawVersion, err)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
var flags swarmbackend.ServiceUpdateOptions
|
|
|
|
// Get returns "" if the header does not exist
|
|
flags.EncodedRegistryAuth = r.Header.Get(registry.AuthHeader)
|
|
flags.RegistryAuthFrom = r.URL.Query().Get("registryAuthFrom")
|
|
flags.Rollback = r.URL.Query().Get("rollback")
|
|
queryRegistry := false
|
|
if v := httputils.VersionFromContext(ctx); v != "" {
|
|
if versions.LessThan(v, "1.30") {
|
|
queryRegistry = true
|
|
}
|
|
adjustForAPIVersion(v, &service)
|
|
}
|
|
|
|
resp, err := sr.backend.UpdateService(vars["id"], version, service, flags, queryRegistry)
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"service-id": vars["id"],
|
|
}).Debug("Error updating service")
|
|
return err
|
|
}
|
|
return httputils.WriteJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
func (sr *swarmRouter) removeService(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := sr.backend.RemoveService(vars["id"]); err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"service-id": vars["id"],
|
|
}).Debug("Error removing service")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) getTaskLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
// make a selector to pass to the helper function
|
|
selector := &backend.LogSelector{
|
|
Tasks: []string{vars["id"]},
|
|
}
|
|
return sr.swarmLogs(ctx, w, r, selector)
|
|
}
|
|
|
|
func (sr *swarmRouter) getServiceLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
// make a selector to pass to the helper function
|
|
selector := &backend.LogSelector{
|
|
Services: []string{vars["id"]},
|
|
}
|
|
return sr.swarmLogs(ctx, w, r, selector)
|
|
}
|
|
|
|
func (sr *swarmRouter) getNodes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
filter, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nodes, err := sr.backend.GetNodes(swarmbackend.NodeListOptions{Filters: filter})
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error getting nodes")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, nodes)
|
|
}
|
|
|
|
func (sr *swarmRouter) getNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
node, err := sr.backend.GetNode(vars["id"])
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"node-id": vars["id"],
|
|
}).Debug("Error getting node")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, node)
|
|
}
|
|
|
|
func (sr *swarmRouter) updateNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var node types.NodeSpec
|
|
if err := httputils.ReadJSON(r, &node); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
err := fmt.Errorf("invalid node version '%s': %v", rawVersion, err)
|
|
return errdefs.InvalidParameter(err)
|
|
}
|
|
|
|
if err := sr.backend.UpdateNode(vars["id"], version, node); err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"node-id": vars["id"],
|
|
}).Debug("Error updating node")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) removeNode(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
|
|
force := httputils.BoolValue(r, "force")
|
|
|
|
if err := sr.backend.RemoveNode(vars["id"], force); err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"node-id": vars["id"],
|
|
}).Debug("Error removing node")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) getTasks(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
filter, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tasks, err := sr.backend.GetTasks(swarmbackend.TaskListOptions{Filters: filter})
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithError(err).Debug("Error getting tasks")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, tasks)
|
|
}
|
|
|
|
func (sr *swarmRouter) getTask(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
task, err := sr.backend.GetTask(vars["id"])
|
|
if err != nil {
|
|
log.G(ctx).WithContext(ctx).WithFields(log.Fields{
|
|
"error": err,
|
|
"task-id": vars["id"],
|
|
}).Debug("Error getting task")
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, task)
|
|
}
|
|
|
|
func (sr *swarmRouter) getSecrets(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
filters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
secrets, err := sr.backend.GetSecrets(swarmbackend.SecretListOptions{Filters: filters})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, secrets)
|
|
}
|
|
|
|
func (sr *swarmRouter) createSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var secret types.SecretSpec
|
|
if err := httputils.ReadJSON(r, &secret); err != nil {
|
|
return err
|
|
}
|
|
version := httputils.VersionFromContext(ctx)
|
|
if secret.Templating != nil && versions.LessThan(version, "1.37") {
|
|
return errdefs.InvalidParameter(errors.Errorf("secret templating is not supported on the specified API version: %s", version))
|
|
}
|
|
|
|
id, err := sr.backend.CreateSecret(secret)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusCreated, &types.SecretCreateResponse{
|
|
ID: id,
|
|
})
|
|
}
|
|
|
|
func (sr *swarmRouter) removeSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := sr.backend.RemoveSecret(vars["id"]); err != nil {
|
|
return err
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) getSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
secret, err := sr.backend.GetSecret(vars["id"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, secret)
|
|
}
|
|
|
|
func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var secret types.SecretSpec
|
|
if err := httputils.ReadJSON(r, &secret); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
return errdefs.InvalidParameter(errors.New("invalid secret version"))
|
|
}
|
|
|
|
id := vars["id"]
|
|
return sr.backend.UpdateSecret(id, version, secret)
|
|
}
|
|
|
|
func (sr *swarmRouter) getConfigs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := httputils.ParseForm(r); err != nil {
|
|
return err
|
|
}
|
|
filters, err := filters.FromJSON(r.Form.Get("filters"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
configs, err := sr.backend.GetConfigs(swarmbackend.ConfigListOptions{Filters: filters})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, configs)
|
|
}
|
|
|
|
func (sr *swarmRouter) createConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var config types.ConfigSpec
|
|
if err := httputils.ReadJSON(r, &config); err != nil {
|
|
return err
|
|
}
|
|
|
|
version := httputils.VersionFromContext(ctx)
|
|
if config.Templating != nil && versions.LessThan(version, "1.37") {
|
|
return errdefs.InvalidParameter(errors.Errorf("config templating is not supported on the specified API version: %s", version))
|
|
}
|
|
|
|
id, err := sr.backend.CreateConfig(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusCreated, &types.ConfigCreateResponse{
|
|
ID: id,
|
|
})
|
|
}
|
|
|
|
func (sr *swarmRouter) removeConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
if err := sr.backend.RemoveConfig(vars["id"]); err != nil {
|
|
return err
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (sr *swarmRouter) getConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
config, err := sr.backend.GetConfig(vars["id"])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return httputils.WriteJSON(w, http.StatusOK, config)
|
|
}
|
|
|
|
func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
var config types.ConfigSpec
|
|
if err := httputils.ReadJSON(r, &config); err != nil {
|
|
return err
|
|
}
|
|
|
|
rawVersion := r.URL.Query().Get("version")
|
|
version, err := strconv.ParseUint(rawVersion, 10, 64)
|
|
if err != nil {
|
|
return errdefs.InvalidParameter(errors.New("invalid config version"))
|
|
}
|
|
|
|
id := vars["id"]
|
|
return sr.backend.UpdateConfig(id, version, config)
|
|
}
|