1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Add config APIs

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2017-03-15 14:52:17 -07:00
parent e3a30ffca6
commit 7728557687
12 changed files with 462 additions and 0 deletions

View file

@ -16,21 +16,32 @@ type Backend interface {
Update(uint64, types.Spec, types.UpdateFlags) error
GetUnlockKey() (string, error)
UnlockSwarm(req types.UnlockRequest) error
GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
GetService(idOrName string, insertDefaults bool) (types.Service, error)
CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error)
UpdateService(string, uint64, types.ServiceSpec, basictypes.ServiceUpdateOptions) (*basictypes.ServiceUpdateResponse, error)
RemoveService(string) error
ServiceLogs(context.Context, *backend.LogSelector, *basictypes.ContainerLogsOptions) (<-chan *backend.LogMessage, error)
GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
GetNode(string) (types.Node, error)
UpdateNode(string, uint64, types.NodeSpec) error
RemoveNode(string, bool) error
GetTasks(basictypes.TaskListOptions) ([]types.Task, error)
GetTask(string) (types.Task, error)
GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error)
CreateSecret(s types.SecretSpec) (string, error)
RemoveSecret(idOrName string) error
GetSecret(id string) (types.Secret, error)
UpdateSecret(idOrName string, version uint64, spec types.SecretSpec) error
GetConfigs(opts basictypes.ConfigListOptions) ([]types.Config, error)
CreateConfig(s types.ConfigSpec) (string, error)
RemoveConfig(id string) error
GetConfig(id string) (types.Config, error)
UpdateConfig(idOrName string, version uint64, spec types.ConfigSpec) error
}

View file

@ -31,23 +31,33 @@ func (sr *swarmRouter) initRoutes() {
router.NewGetRoute("/swarm/unlockkey", sr.getUnlockKey),
router.NewPostRoute("/swarm/update", sr.updateCluster),
router.NewPostRoute("/swarm/unlock", sr.unlockCluster),
router.NewGetRoute("/services", sr.getServices),
router.NewGetRoute("/services/{id}", sr.getService),
router.NewPostRoute("/services/create", sr.createService),
router.NewPostRoute("/services/{id}/update", sr.updateService),
router.NewDeleteRoute("/services/{id}", sr.removeService),
router.NewGetRoute("/services/{id}/logs", sr.getServiceLogs, router.WithCancel),
router.NewGetRoute("/nodes", sr.getNodes),
router.NewGetRoute("/nodes/{id}", sr.getNode),
router.NewDeleteRoute("/nodes/{id}", sr.removeNode),
router.NewPostRoute("/nodes/{id}/update", sr.updateNode),
router.NewGetRoute("/tasks", sr.getTasks),
router.NewGetRoute("/tasks/{id}", sr.getTask),
router.NewGetRoute("/tasks/{id}/logs", sr.getTaskLogs, router.WithCancel),
router.NewGetRoute("/secrets", sr.getSecrets),
router.NewPostRoute("/secrets/create", sr.createSecret),
router.NewDeleteRoute("/secrets/{id}", sr.removeSecret),
router.NewGetRoute("/secrets/{id}", sr.getSecret),
router.NewPostRoute("/secrets/{id}/update", sr.updateSecret),
router.NewGetRoute("/configs", sr.getConfigs),
router.NewPostRoute("/configs/create", sr.createConfig),
router.NewDeleteRoute("/configs/{id}", sr.removeConfig),
router.NewGetRoute("/configs/{id}", sr.getConfig),
router.NewPostRoute("/configs/{id}/update", sr.updateConfig),
}
}

View file

@ -408,3 +408,74 @@ func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter,
return nil
}
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.FromParam(r.Form.Get("filters"))
if err != nil {
return err
}
configs, err := sr.backend.GetConfigs(basictypes.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 := json.NewDecoder(r.Body).Decode(&config); err != nil {
return err
}
id, err := sr.backend.CreateConfig(config)
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusCreated, &basictypes.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 := json.NewDecoder(r.Body).Decode(&config); err != nil {
return errors.NewBadRequestError(err)
}
rawVersion := r.URL.Query().Get("version")
version, err := strconv.ParseUint(rawVersion, 10, 64)
if err != nil {
return errors.NewBadRequestError(fmt.Errorf("invalid config version"))
}
id := vars["id"]
if err := sr.backend.UpdateConfig(id, version, config); err != nil {
return err
}
return nil
}

31
api/types/swarm/config.go Normal file
View file

@ -0,0 +1,31 @@
package swarm
import "os"
// Config represents a config.
type Config struct {
ID string
Meta
Spec ConfigSpec
}
// ConfigSpec represents a config specification from a config in swarm
type ConfigSpec struct {
Annotations
Data []byte `json:",omitempty"`
}
// ConfigReferenceFileTarget is a file target in a config reference
type ConfigReferenceFileTarget struct {
Name string
UID string
GID string
Mode os.FileMode
}
// ConfigReference is a reference to a config in swarm
type ConfigReference struct {
File *ConfigReferenceFileTarget
ConfigID string
ConfigName string
}

View file

@ -68,4 +68,5 @@ type ContainerSpec struct {
Hosts []string `json:",omitempty"`
DNSConfig *DNSConfig `json:",omitempty"`
Secrets []*SecretReference `json:",omitempty"`
Configs []*ConfigReference `json:",omitempty"`
}

View file

@ -522,6 +522,18 @@ type SecretListOptions struct {
Filters filters.Args
}
// ConfigCreateResponse contains the information returned to a client
// on the creation of a new config.
type ConfigCreateResponse struct {
// ID is the id of the created config.
ID string
}
// ConfigListOptions holds parameters to list configs
type ConfigListOptions struct {
Filters filters.Args
}
// PushResult contains the tag, manifest digest, and manifest size from the
// push. It's used to signal this information to the trust code in the client
// so it can sign the manifest if necessary.

117
daemon/cluster/configs.go Normal file
View file

@ -0,0 +1,117 @@
package cluster
import (
apitypes "github.com/docker/docker/api/types"
types "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/daemon/cluster/convert"
swarmapi "github.com/docker/swarmkit/api"
"golang.org/x/net/context"
)
// GetConfig returns a config from a managed swarm cluster
func (c *Cluster) GetConfig(input string) (types.Config, error) {
var config *swarmapi.Config
if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
s, err := getConfig(ctx, state.controlClient, input)
if err != nil {
return err
}
config = s
return nil
}); err != nil {
return types.Config{}, err
}
return convert.ConfigFromGRPC(config), nil
}
// GetConfigs returns all configs of a managed swarm cluster.
func (c *Cluster) GetConfigs(options apitypes.ConfigListOptions) ([]types.Config, error) {
c.mu.RLock()
defer c.mu.RUnlock()
state := c.currentNodeState()
if !state.IsActiveManager() {
return nil, c.errNoManager(state)
}
filters, err := newListConfigsFilters(options.Filters)
if err != nil {
return nil, err
}
ctx, cancel := c.getRequestContext()
defer cancel()
r, err := state.controlClient.ListConfigs(ctx,
&swarmapi.ListConfigsRequest{Filters: filters})
if err != nil {
return nil, err
}
configs := []types.Config{}
for _, config := range r.Configs {
configs = append(configs, convert.ConfigFromGRPC(config))
}
return configs, nil
}
// CreateConfig creates a new config in a managed swarm cluster.
func (c *Cluster) CreateConfig(s types.ConfigSpec) (string, error) {
var resp *swarmapi.CreateConfigResponse
if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
configSpec := convert.ConfigSpecToGRPC(s)
r, err := state.controlClient.CreateConfig(ctx,
&swarmapi.CreateConfigRequest{Spec: &configSpec})
if err != nil {
return err
}
resp = r
return nil
}); err != nil {
return "", err
}
return resp.Config.ID, nil
}
// RemoveConfig removes a config from a managed swarm cluster.
func (c *Cluster) RemoveConfig(input string) error {
return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
config, err := getConfig(ctx, state.controlClient, input)
if err != nil {
return err
}
req := &swarmapi.RemoveConfigRequest{
ConfigID: config.ID,
}
_, err = state.controlClient.RemoveConfig(ctx, req)
return err
})
}
// UpdateConfig updates a config in a managed swarm cluster.
// Note: this is not exposed to the CLI but is available from the API only
func (c *Cluster) UpdateConfig(input string, version uint64, spec types.ConfigSpec) error {
return c.lockedManagerAction(func(ctx context.Context, state nodeState) error {
config, err := getConfig(ctx, state.controlClient, input)
if err != nil {
return err
}
configSpec := convert.ConfigSpecToGRPC(spec)
_, err = state.controlClient.UpdateConfig(ctx,
&swarmapi.UpdateConfigRequest{
ConfigID: config.ID,
ConfigVersion: &swarmapi.Version{
Index: version,
},
Spec: &configSpec,
})
return err
})
}

View file

@ -0,0 +1,61 @@
package convert
import (
swarmtypes "github.com/docker/docker/api/types/swarm"
swarmapi "github.com/docker/swarmkit/api"
gogotypes "github.com/gogo/protobuf/types"
)
// ConfigFromGRPC converts a grpc Config to a Config.
func ConfigFromGRPC(s *swarmapi.Config) swarmtypes.Config {
config := swarmtypes.Config{
ID: s.ID,
Spec: swarmtypes.ConfigSpec{
Annotations: annotationsFromGRPC(s.Spec.Annotations),
Data: s.Spec.Data,
},
}
config.Version.Index = s.Meta.Version.Index
// Meta
config.CreatedAt, _ = gogotypes.TimestampFromProto(s.Meta.CreatedAt)
config.UpdatedAt, _ = gogotypes.TimestampFromProto(s.Meta.UpdatedAt)
return config
}
// ConfigSpecToGRPC converts Config to a grpc Config.
func ConfigSpecToGRPC(s swarmtypes.ConfigSpec) swarmapi.ConfigSpec {
return swarmapi.ConfigSpec{
Annotations: swarmapi.Annotations{
Name: s.Name,
Labels: s.Labels,
},
Data: s.Data,
}
}
// ConfigReferencesFromGRPC converts a slice of grpc ConfigReference to ConfigReference
func ConfigReferencesFromGRPC(s []*swarmapi.ConfigReference) []*swarmtypes.ConfigReference {
refs := []*swarmtypes.ConfigReference{}
for _, r := range s {
ref := &swarmtypes.ConfigReference{
ConfigID: r.ConfigID,
ConfigName: r.ConfigName,
}
if t, ok := r.Target.(*swarmapi.ConfigReference_File); ok {
ref.File = &swarmtypes.ConfigReferenceFileTarget{
Name: t.File.Name,
UID: t.File.UID,
GID: t.File.GID,
Mode: t.File.Mode,
}
}
refs = append(refs, ref)
}
return refs
}

View file

@ -30,6 +30,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
ReadOnly: c.ReadOnly,
Hosts: c.Hosts,
Secrets: secretReferencesFromGRPC(c.Secrets),
Configs: configReferencesFromGRPC(c.Configs),
}
if c.DNSConfig != nil {
@ -137,6 +138,7 @@ func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretRefer
return refs
}
func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretReference {
refs := make([]*types.SecretReference, 0, len(sr))
for _, s := range sr {
@ -161,6 +163,54 @@ func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretRef
return refs
}
func configReferencesToGRPC(sr []*types.ConfigReference) []*swarmapi.ConfigReference {
refs := make([]*swarmapi.ConfigReference, 0, len(sr))
for _, s := range sr {
ref := &swarmapi.ConfigReference{
ConfigID: s.ConfigID,
ConfigName: s.ConfigName,
}
if s.File != nil {
ref.Target = &swarmapi.ConfigReference_File{
File: &swarmapi.FileTarget{
Name: s.File.Name,
UID: s.File.UID,
GID: s.File.GID,
Mode: s.File.Mode,
},
}
}
refs = append(refs, ref)
}
return refs
}
func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference {
refs := make([]*types.ConfigReference, 0, len(sr))
for _, s := range sr {
target := s.GetFile()
if target == nil {
// not a file target
logrus.Warnf("config target not a file: config=%s", s.ConfigID)
continue
}
refs = append(refs, &types.ConfigReference{
File: &types.ConfigReferenceFileTarget{
Name: target.Name,
UID: target.UID,
GID: target.GID,
Mode: target.Mode,
},
ConfigID: s.ConfigID,
ConfigName: s.ConfigName,
})
}
return refs
}
func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
containerSpec := &swarmapi.ContainerSpec{
Image: c.Image,
@ -178,6 +228,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
ReadOnly: c.ReadOnly,
Hosts: c.Hosts,
Secrets: secretReferencesToGRPC(c.Secrets),
Configs: configReferencesToGRPC(c.Configs),
}
if c.DNSConfig != nil {

View file

@ -103,3 +103,19 @@ func newListSecretsFilters(filter filters.Args) (*swarmapi.ListSecretsRequest_Fi
Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")),
}, nil
}
func newListConfigsFilters(filter filters.Args) (*swarmapi.ListConfigsRequest_Filters, error) {
accepted := map[string]bool{
"name": true,
"id": true,
"label": true,
}
if err := filter.Validate(accepted); err != nil {
return nil, err
}
return &swarmapi.ListConfigsRequest_Filters{
NamePrefixes: filter.Get("name"),
IDPrefixes: filter.Get("id"),
Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")),
}, nil
}

View file

@ -55,3 +55,48 @@ func TestNewListSecretsFilters(t *testing.T) {
}
}
}
func TestNewListConfigsFilters(t *testing.T) {
validNameFilter := filters.NewArgs()
validNameFilter.Add("name", "test_name")
validIDFilter := filters.NewArgs()
validIDFilter.Add("id", "7c9009d6720f6de3b492f5")
validLabelFilter := filters.NewArgs()
validLabelFilter.Add("label", "type=test")
validLabelFilter.Add("label", "storage=ssd")
validLabelFilter.Add("label", "memory")
validAllFilter := filters.NewArgs()
validAllFilter.Add("name", "nodeName")
validAllFilter.Add("id", "7c9009d6720f6de3b492f5")
validAllFilter.Add("label", "type=test")
validAllFilter.Add("label", "memory")
validFilters := []filters.Args{
validNameFilter,
validIDFilter,
validLabelFilter,
validAllFilter,
}
invalidTypeFilter := filters.NewArgs()
invalidTypeFilter.Add("nonexist", "aaaa")
invalidFilters := []filters.Args{
invalidTypeFilter,
}
for _, filter := range validFilters {
if _, err := newListConfigsFilters(filter); err != nil {
t.Fatalf("Should get no error, got %v", err)
}
}
for _, filter := range invalidFilters {
if _, err := newListConfigsFilters(filter); err == nil {
t.Fatalf("Should get an error for filter %s, while got nil", filter)
}
}
}

View file

@ -174,6 +174,42 @@ func getSecret(ctx context.Context, c swarmapi.ControlClient, input string) (*sw
return rl.Secrets[0], nil
}
func getConfig(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Config, error) {
// attempt to lookup config by full ID
if rg, err := c.GetConfig(ctx, &swarmapi.GetConfigRequest{ConfigID: input}); err == nil {
return rg.Config, nil
}
// If any error (including NotFound), ListConfigs to match via full name.
rl, err := c.ListConfigs(ctx, &swarmapi.ListConfigsRequest{
Filters: &swarmapi.ListConfigsRequest_Filters{
Names: []string{input},
},
})
if err != nil || len(rl.Configs) == 0 {
// If any error or 0 result, ListConfigs to match via ID prefix.
rl, err = c.ListConfigs(ctx, &swarmapi.ListConfigsRequest{
Filters: &swarmapi.ListConfigsRequest_Filters{
IDPrefixes: []string{input},
},
})
}
if err != nil {
return nil, err
}
if len(rl.Configs) == 0 {
err := fmt.Errorf("config %s not found", input)
return nil, errors.NewRequestNotFoundError(err)
}
if l := len(rl.Configs); l > 1 {
return nil, fmt.Errorf("config %s is ambiguous (%d matches found)", input, l)
}
return rl.Configs[0], nil
}
func getNetwork(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Network, error) {
// GetNetwork to match via full ID.
if rg, err := c.GetNetwork(ctx, &swarmapi.GetNetworkRequest{NetworkID: input}); err == nil {