Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann authored on 2017/03/16 06:52:17... | ... |
@@ -16,21 +16,32 @@ type Backend interface { |
16 | 16 |
Update(uint64, types.Spec, types.UpdateFlags) error |
17 | 17 |
GetUnlockKey() (string, error) |
18 | 18 |
UnlockSwarm(req types.UnlockRequest) error |
19 |
+ |
|
19 | 20 |
GetServices(basictypes.ServiceListOptions) ([]types.Service, error) |
20 | 21 |
GetService(idOrName string, insertDefaults bool) (types.Service, error) |
21 | 22 |
CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error) |
22 | 23 |
UpdateService(string, uint64, types.ServiceSpec, basictypes.ServiceUpdateOptions) (*basictypes.ServiceUpdateResponse, error) |
23 | 24 |
RemoveService(string) error |
25 |
+ |
|
24 | 26 |
ServiceLogs(context.Context, *backend.LogSelector, *basictypes.ContainerLogsOptions) (<-chan *backend.LogMessage, error) |
27 |
+ |
|
25 | 28 |
GetNodes(basictypes.NodeListOptions) ([]types.Node, error) |
26 | 29 |
GetNode(string) (types.Node, error) |
27 | 30 |
UpdateNode(string, uint64, types.NodeSpec) error |
28 | 31 |
RemoveNode(string, bool) error |
32 |
+ |
|
29 | 33 |
GetTasks(basictypes.TaskListOptions) ([]types.Task, error) |
30 | 34 |
GetTask(string) (types.Task, error) |
35 |
+ |
|
31 | 36 |
GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error) |
32 | 37 |
CreateSecret(s types.SecretSpec) (string, error) |
33 | 38 |
RemoveSecret(idOrName string) error |
34 | 39 |
GetSecret(id string) (types.Secret, error) |
35 | 40 |
UpdateSecret(idOrName string, version uint64, spec types.SecretSpec) error |
41 |
+ |
|
42 |
+ GetConfigs(opts basictypes.ConfigListOptions) ([]types.Config, error) |
|
43 |
+ CreateConfig(s types.ConfigSpec) (string, error) |
|
44 |
+ RemoveConfig(id string) error |
|
45 |
+ GetConfig(id string) (types.Config, error) |
|
46 |
+ UpdateConfig(idOrName string, version uint64, spec types.ConfigSpec) error |
|
36 | 47 |
} |
... | ... |
@@ -31,23 +31,33 @@ func (sr *swarmRouter) initRoutes() { |
31 | 31 |
router.NewGetRoute("/swarm/unlockkey", sr.getUnlockKey), |
32 | 32 |
router.NewPostRoute("/swarm/update", sr.updateCluster), |
33 | 33 |
router.NewPostRoute("/swarm/unlock", sr.unlockCluster), |
34 |
+ |
|
34 | 35 |
router.NewGetRoute("/services", sr.getServices), |
35 | 36 |
router.NewGetRoute("/services/{id}", sr.getService), |
36 | 37 |
router.NewPostRoute("/services/create", sr.createService), |
37 | 38 |
router.NewPostRoute("/services/{id}/update", sr.updateService), |
38 | 39 |
router.NewDeleteRoute("/services/{id}", sr.removeService), |
39 | 40 |
router.NewGetRoute("/services/{id}/logs", sr.getServiceLogs, router.WithCancel), |
41 |
+ |
|
40 | 42 |
router.NewGetRoute("/nodes", sr.getNodes), |
41 | 43 |
router.NewGetRoute("/nodes/{id}", sr.getNode), |
42 | 44 |
router.NewDeleteRoute("/nodes/{id}", sr.removeNode), |
43 | 45 |
router.NewPostRoute("/nodes/{id}/update", sr.updateNode), |
46 |
+ |
|
44 | 47 |
router.NewGetRoute("/tasks", sr.getTasks), |
45 | 48 |
router.NewGetRoute("/tasks/{id}", sr.getTask), |
46 | 49 |
router.NewGetRoute("/tasks/{id}/logs", sr.getTaskLogs, router.WithCancel), |
50 |
+ |
|
47 | 51 |
router.NewGetRoute("/secrets", sr.getSecrets), |
48 | 52 |
router.NewPostRoute("/secrets/create", sr.createSecret), |
49 | 53 |
router.NewDeleteRoute("/secrets/{id}", sr.removeSecret), |
50 | 54 |
router.NewGetRoute("/secrets/{id}", sr.getSecret), |
51 | 55 |
router.NewPostRoute("/secrets/{id}/update", sr.updateSecret), |
56 |
+ |
|
57 |
+ router.NewGetRoute("/configs", sr.getConfigs), |
|
58 |
+ router.NewPostRoute("/configs/create", sr.createConfig), |
|
59 |
+ router.NewDeleteRoute("/configs/{id}", sr.removeConfig), |
|
60 |
+ router.NewGetRoute("/configs/{id}", sr.getConfig), |
|
61 |
+ router.NewPostRoute("/configs/{id}/update", sr.updateConfig), |
|
52 | 62 |
} |
53 | 63 |
} |
... | ... |
@@ -408,3 +408,74 @@ func (sr *swarmRouter) updateSecret(ctx context.Context, w http.ResponseWriter, |
408 | 408 |
|
409 | 409 |
return nil |
410 | 410 |
} |
411 |
+ |
|
412 |
+func (sr *swarmRouter) getConfigs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
413 |
+ if err := httputils.ParseForm(r); err != nil { |
|
414 |
+ return err |
|
415 |
+ } |
|
416 |
+ filters, err := filters.FromParam(r.Form.Get("filters")) |
|
417 |
+ if err != nil { |
|
418 |
+ return err |
|
419 |
+ } |
|
420 |
+ |
|
421 |
+ configs, err := sr.backend.GetConfigs(basictypes.ConfigListOptions{Filters: filters}) |
|
422 |
+ if err != nil { |
|
423 |
+ return err |
|
424 |
+ } |
|
425 |
+ |
|
426 |
+ return httputils.WriteJSON(w, http.StatusOK, configs) |
|
427 |
+} |
|
428 |
+ |
|
429 |
+func (sr *swarmRouter) createConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
430 |
+ var config types.ConfigSpec |
|
431 |
+ if err := json.NewDecoder(r.Body).Decode(&config); err != nil { |
|
432 |
+ return err |
|
433 |
+ } |
|
434 |
+ |
|
435 |
+ id, err := sr.backend.CreateConfig(config) |
|
436 |
+ if err != nil { |
|
437 |
+ return err |
|
438 |
+ } |
|
439 |
+ |
|
440 |
+ return httputils.WriteJSON(w, http.StatusCreated, &basictypes.ConfigCreateResponse{ |
|
441 |
+ ID: id, |
|
442 |
+ }) |
|
443 |
+} |
|
444 |
+ |
|
445 |
+func (sr *swarmRouter) removeConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
446 |
+ if err := sr.backend.RemoveConfig(vars["id"]); err != nil { |
|
447 |
+ return err |
|
448 |
+ } |
|
449 |
+ w.WriteHeader(http.StatusNoContent) |
|
450 |
+ |
|
451 |
+ return nil |
|
452 |
+} |
|
453 |
+ |
|
454 |
+func (sr *swarmRouter) getConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
455 |
+ config, err := sr.backend.GetConfig(vars["id"]) |
|
456 |
+ if err != nil { |
|
457 |
+ return err |
|
458 |
+ } |
|
459 |
+ |
|
460 |
+ return httputils.WriteJSON(w, http.StatusOK, config) |
|
461 |
+} |
|
462 |
+ |
|
463 |
+func (sr *swarmRouter) updateConfig(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { |
|
464 |
+ var config types.ConfigSpec |
|
465 |
+ if err := json.NewDecoder(r.Body).Decode(&config); err != nil { |
|
466 |
+ return errors.NewBadRequestError(err) |
|
467 |
+ } |
|
468 |
+ |
|
469 |
+ rawVersion := r.URL.Query().Get("version") |
|
470 |
+ version, err := strconv.ParseUint(rawVersion, 10, 64) |
|
471 |
+ if err != nil { |
|
472 |
+ return errors.NewBadRequestError(fmt.Errorf("invalid config version")) |
|
473 |
+ } |
|
474 |
+ |
|
475 |
+ id := vars["id"] |
|
476 |
+ if err := sr.backend.UpdateConfig(id, version, config); err != nil { |
|
477 |
+ return err |
|
478 |
+ } |
|
479 |
+ |
|
480 |
+ return nil |
|
481 |
+} |
411 | 482 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,31 @@ |
0 |
+package swarm |
|
1 |
+ |
|
2 |
+import "os" |
|
3 |
+ |
|
4 |
+// Config represents a config. |
|
5 |
+type Config struct { |
|
6 |
+ ID string |
|
7 |
+ Meta |
|
8 |
+ Spec ConfigSpec |
|
9 |
+} |
|
10 |
+ |
|
11 |
+// ConfigSpec represents a config specification from a config in swarm |
|
12 |
+type ConfigSpec struct { |
|
13 |
+ Annotations |
|
14 |
+ Data []byte `json:",omitempty"` |
|
15 |
+} |
|
16 |
+ |
|
17 |
+// ConfigReferenceFileTarget is a file target in a config reference |
|
18 |
+type ConfigReferenceFileTarget struct { |
|
19 |
+ Name string |
|
20 |
+ UID string |
|
21 |
+ GID string |
|
22 |
+ Mode os.FileMode |
|
23 |
+} |
|
24 |
+ |
|
25 |
+// ConfigReference is a reference to a config in swarm |
|
26 |
+type ConfigReference struct { |
|
27 |
+ File *ConfigReferenceFileTarget |
|
28 |
+ ConfigID string |
|
29 |
+ ConfigName string |
|
30 |
+} |
... | ... |
@@ -522,6 +522,18 @@ type SecretListOptions struct { |
522 | 522 |
Filters filters.Args |
523 | 523 |
} |
524 | 524 |
|
525 |
+// ConfigCreateResponse contains the information returned to a client |
|
526 |
+// on the creation of a new config. |
|
527 |
+type ConfigCreateResponse struct { |
|
528 |
+ // ID is the id of the created config. |
|
529 |
+ ID string |
|
530 |
+} |
|
531 |
+ |
|
532 |
+// ConfigListOptions holds parameters to list configs |
|
533 |
+type ConfigListOptions struct { |
|
534 |
+ Filters filters.Args |
|
535 |
+} |
|
536 |
+ |
|
525 | 537 |
// PushResult contains the tag, manifest digest, and manifest size from the |
526 | 538 |
// push. It's used to signal this information to the trust code in the client |
527 | 539 |
// so it can sign the manifest if necessary. |
528 | 540 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,117 @@ |
0 |
+package cluster |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ apitypes "github.com/docker/docker/api/types" |
|
4 |
+ types "github.com/docker/docker/api/types/swarm" |
|
5 |
+ "github.com/docker/docker/daemon/cluster/convert" |
|
6 |
+ swarmapi "github.com/docker/swarmkit/api" |
|
7 |
+ "golang.org/x/net/context" |
|
8 |
+) |
|
9 |
+ |
|
10 |
+// GetConfig returns a config from a managed swarm cluster |
|
11 |
+func (c *Cluster) GetConfig(input string) (types.Config, error) { |
|
12 |
+ var config *swarmapi.Config |
|
13 |
+ |
|
14 |
+ if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error { |
|
15 |
+ s, err := getConfig(ctx, state.controlClient, input) |
|
16 |
+ if err != nil { |
|
17 |
+ return err |
|
18 |
+ } |
|
19 |
+ config = s |
|
20 |
+ return nil |
|
21 |
+ }); err != nil { |
|
22 |
+ return types.Config{}, err |
|
23 |
+ } |
|
24 |
+ return convert.ConfigFromGRPC(config), nil |
|
25 |
+} |
|
26 |
+ |
|
27 |
+// GetConfigs returns all configs of a managed swarm cluster. |
|
28 |
+func (c *Cluster) GetConfigs(options apitypes.ConfigListOptions) ([]types.Config, error) { |
|
29 |
+ c.mu.RLock() |
|
30 |
+ defer c.mu.RUnlock() |
|
31 |
+ |
|
32 |
+ state := c.currentNodeState() |
|
33 |
+ if !state.IsActiveManager() { |
|
34 |
+ return nil, c.errNoManager(state) |
|
35 |
+ } |
|
36 |
+ |
|
37 |
+ filters, err := newListConfigsFilters(options.Filters) |
|
38 |
+ if err != nil { |
|
39 |
+ return nil, err |
|
40 |
+ } |
|
41 |
+ ctx, cancel := c.getRequestContext() |
|
42 |
+ defer cancel() |
|
43 |
+ |
|
44 |
+ r, err := state.controlClient.ListConfigs(ctx, |
|
45 |
+ &swarmapi.ListConfigsRequest{Filters: filters}) |
|
46 |
+ if err != nil { |
|
47 |
+ return nil, err |
|
48 |
+ } |
|
49 |
+ |
|
50 |
+ configs := []types.Config{} |
|
51 |
+ |
|
52 |
+ for _, config := range r.Configs { |
|
53 |
+ configs = append(configs, convert.ConfigFromGRPC(config)) |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ return configs, nil |
|
57 |
+} |
|
58 |
+ |
|
59 |
+// CreateConfig creates a new config in a managed swarm cluster. |
|
60 |
+func (c *Cluster) CreateConfig(s types.ConfigSpec) (string, error) { |
|
61 |
+ var resp *swarmapi.CreateConfigResponse |
|
62 |
+ if err := c.lockedManagerAction(func(ctx context.Context, state nodeState) error { |
|
63 |
+ configSpec := convert.ConfigSpecToGRPC(s) |
|
64 |
+ |
|
65 |
+ r, err := state.controlClient.CreateConfig(ctx, |
|
66 |
+ &swarmapi.CreateConfigRequest{Spec: &configSpec}) |
|
67 |
+ if err != nil { |
|
68 |
+ return err |
|
69 |
+ } |
|
70 |
+ resp = r |
|
71 |
+ return nil |
|
72 |
+ }); err != nil { |
|
73 |
+ return "", err |
|
74 |
+ } |
|
75 |
+ return resp.Config.ID, nil |
|
76 |
+} |
|
77 |
+ |
|
78 |
+// RemoveConfig removes a config from a managed swarm cluster. |
|
79 |
+func (c *Cluster) RemoveConfig(input string) error { |
|
80 |
+ return c.lockedManagerAction(func(ctx context.Context, state nodeState) error { |
|
81 |
+ config, err := getConfig(ctx, state.controlClient, input) |
|
82 |
+ if err != nil { |
|
83 |
+ return err |
|
84 |
+ } |
|
85 |
+ |
|
86 |
+ req := &swarmapi.RemoveConfigRequest{ |
|
87 |
+ ConfigID: config.ID, |
|
88 |
+ } |
|
89 |
+ |
|
90 |
+ _, err = state.controlClient.RemoveConfig(ctx, req) |
|
91 |
+ return err |
|
92 |
+ }) |
|
93 |
+} |
|
94 |
+ |
|
95 |
+// UpdateConfig updates a config in a managed swarm cluster. |
|
96 |
+// Note: this is not exposed to the CLI but is available from the API only |
|
97 |
+func (c *Cluster) UpdateConfig(input string, version uint64, spec types.ConfigSpec) error { |
|
98 |
+ return c.lockedManagerAction(func(ctx context.Context, state nodeState) error { |
|
99 |
+ config, err := getConfig(ctx, state.controlClient, input) |
|
100 |
+ if err != nil { |
|
101 |
+ return err |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ configSpec := convert.ConfigSpecToGRPC(spec) |
|
105 |
+ |
|
106 |
+ _, err = state.controlClient.UpdateConfig(ctx, |
|
107 |
+ &swarmapi.UpdateConfigRequest{ |
|
108 |
+ ConfigID: config.ID, |
|
109 |
+ ConfigVersion: &swarmapi.Version{ |
|
110 |
+ Index: version, |
|
111 |
+ }, |
|
112 |
+ Spec: &configSpec, |
|
113 |
+ }) |
|
114 |
+ return err |
|
115 |
+ }) |
|
116 |
+} |
0 | 117 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,61 @@ |
0 |
+package convert |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ swarmtypes "github.com/docker/docker/api/types/swarm" |
|
4 |
+ swarmapi "github.com/docker/swarmkit/api" |
|
5 |
+ gogotypes "github.com/gogo/protobuf/types" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+// ConfigFromGRPC converts a grpc Config to a Config. |
|
9 |
+func ConfigFromGRPC(s *swarmapi.Config) swarmtypes.Config { |
|
10 |
+ config := swarmtypes.Config{ |
|
11 |
+ ID: s.ID, |
|
12 |
+ Spec: swarmtypes.ConfigSpec{ |
|
13 |
+ Annotations: annotationsFromGRPC(s.Spec.Annotations), |
|
14 |
+ Data: s.Spec.Data, |
|
15 |
+ }, |
|
16 |
+ } |
|
17 |
+ |
|
18 |
+ config.Version.Index = s.Meta.Version.Index |
|
19 |
+ // Meta |
|
20 |
+ config.CreatedAt, _ = gogotypes.TimestampFromProto(s.Meta.CreatedAt) |
|
21 |
+ config.UpdatedAt, _ = gogotypes.TimestampFromProto(s.Meta.UpdatedAt) |
|
22 |
+ |
|
23 |
+ return config |
|
24 |
+} |
|
25 |
+ |
|
26 |
+// ConfigSpecToGRPC converts Config to a grpc Config. |
|
27 |
+func ConfigSpecToGRPC(s swarmtypes.ConfigSpec) swarmapi.ConfigSpec { |
|
28 |
+ return swarmapi.ConfigSpec{ |
|
29 |
+ Annotations: swarmapi.Annotations{ |
|
30 |
+ Name: s.Name, |
|
31 |
+ Labels: s.Labels, |
|
32 |
+ }, |
|
33 |
+ Data: s.Data, |
|
34 |
+ } |
|
35 |
+} |
|
36 |
+ |
|
37 |
+// ConfigReferencesFromGRPC converts a slice of grpc ConfigReference to ConfigReference |
|
38 |
+func ConfigReferencesFromGRPC(s []*swarmapi.ConfigReference) []*swarmtypes.ConfigReference { |
|
39 |
+ refs := []*swarmtypes.ConfigReference{} |
|
40 |
+ |
|
41 |
+ for _, r := range s { |
|
42 |
+ ref := &swarmtypes.ConfigReference{ |
|
43 |
+ ConfigID: r.ConfigID, |
|
44 |
+ ConfigName: r.ConfigName, |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ if t, ok := r.Target.(*swarmapi.ConfigReference_File); ok { |
|
48 |
+ ref.File = &swarmtypes.ConfigReferenceFileTarget{ |
|
49 |
+ Name: t.File.Name, |
|
50 |
+ UID: t.File.UID, |
|
51 |
+ GID: t.File.GID, |
|
52 |
+ Mode: t.File.Mode, |
|
53 |
+ } |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ refs = append(refs, ref) |
|
57 |
+ } |
|
58 |
+ |
|
59 |
+ return refs |
|
60 |
+} |
... | ... |
@@ -30,6 +30,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec { |
30 | 30 |
ReadOnly: c.ReadOnly, |
31 | 31 |
Hosts: c.Hosts, |
32 | 32 |
Secrets: secretReferencesFromGRPC(c.Secrets), |
33 |
+ Configs: configReferencesFromGRPC(c.Configs), |
|
33 | 34 |
} |
34 | 35 |
|
35 | 36 |
if c.DNSConfig != nil { |
... | ... |
@@ -137,6 +138,7 @@ func secretReferencesToGRPC(sr []*types.SecretReference) []*swarmapi.SecretRefer |
137 | 137 |
|
138 | 138 |
return refs |
139 | 139 |
} |
140 |
+ |
|
140 | 141 |
func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretReference { |
141 | 142 |
refs := make([]*types.SecretReference, 0, len(sr)) |
142 | 143 |
for _, s := range sr { |
... | ... |
@@ -161,6 +163,54 @@ func secretReferencesFromGRPC(sr []*swarmapi.SecretReference) []*types.SecretRef |
161 | 161 |
return refs |
162 | 162 |
} |
163 | 163 |
|
164 |
+func configReferencesToGRPC(sr []*types.ConfigReference) []*swarmapi.ConfigReference { |
|
165 |
+ refs := make([]*swarmapi.ConfigReference, 0, len(sr)) |
|
166 |
+ for _, s := range sr { |
|
167 |
+ ref := &swarmapi.ConfigReference{ |
|
168 |
+ ConfigID: s.ConfigID, |
|
169 |
+ ConfigName: s.ConfigName, |
|
170 |
+ } |
|
171 |
+ if s.File != nil { |
|
172 |
+ ref.Target = &swarmapi.ConfigReference_File{ |
|
173 |
+ File: &swarmapi.FileTarget{ |
|
174 |
+ Name: s.File.Name, |
|
175 |
+ UID: s.File.UID, |
|
176 |
+ GID: s.File.GID, |
|
177 |
+ Mode: s.File.Mode, |
|
178 |
+ }, |
|
179 |
+ } |
|
180 |
+ } |
|
181 |
+ |
|
182 |
+ refs = append(refs, ref) |
|
183 |
+ } |
|
184 |
+ |
|
185 |
+ return refs |
|
186 |
+} |
|
187 |
+ |
|
188 |
+func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigReference { |
|
189 |
+ refs := make([]*types.ConfigReference, 0, len(sr)) |
|
190 |
+ for _, s := range sr { |
|
191 |
+ target := s.GetFile() |
|
192 |
+ if target == nil { |
|
193 |
+ // not a file target |
|
194 |
+ logrus.Warnf("config target not a file: config=%s", s.ConfigID) |
|
195 |
+ continue |
|
196 |
+ } |
|
197 |
+ refs = append(refs, &types.ConfigReference{ |
|
198 |
+ File: &types.ConfigReferenceFileTarget{ |
|
199 |
+ Name: target.Name, |
|
200 |
+ UID: target.UID, |
|
201 |
+ GID: target.GID, |
|
202 |
+ Mode: target.Mode, |
|
203 |
+ }, |
|
204 |
+ ConfigID: s.ConfigID, |
|
205 |
+ ConfigName: s.ConfigName, |
|
206 |
+ }) |
|
207 |
+ } |
|
208 |
+ |
|
209 |
+ return refs |
|
210 |
+} |
|
211 |
+ |
|
164 | 212 |
func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) { |
165 | 213 |
containerSpec := &swarmapi.ContainerSpec{ |
166 | 214 |
Image: c.Image, |
... | ... |
@@ -178,6 +228,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) { |
178 | 178 |
ReadOnly: c.ReadOnly, |
179 | 179 |
Hosts: c.Hosts, |
180 | 180 |
Secrets: secretReferencesToGRPC(c.Secrets), |
181 |
+ Configs: configReferencesToGRPC(c.Configs), |
|
181 | 182 |
} |
182 | 183 |
|
183 | 184 |
if c.DNSConfig != nil { |
... | ... |
@@ -103,3 +103,19 @@ func newListSecretsFilters(filter filters.Args) (*swarmapi.ListSecretsRequest_Fi |
103 | 103 |
Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")), |
104 | 104 |
}, nil |
105 | 105 |
} |
106 |
+ |
|
107 |
+func newListConfigsFilters(filter filters.Args) (*swarmapi.ListConfigsRequest_Filters, error) { |
|
108 |
+ accepted := map[string]bool{ |
|
109 |
+ "name": true, |
|
110 |
+ "id": true, |
|
111 |
+ "label": true, |
|
112 |
+ } |
|
113 |
+ if err := filter.Validate(accepted); err != nil { |
|
114 |
+ return nil, err |
|
115 |
+ } |
|
116 |
+ return &swarmapi.ListConfigsRequest_Filters{ |
|
117 |
+ NamePrefixes: filter.Get("name"), |
|
118 |
+ IDPrefixes: filter.Get("id"), |
|
119 |
+ Labels: runconfigopts.ConvertKVStringsToMap(filter.Get("label")), |
|
120 |
+ }, nil |
|
121 |
+} |
... | ... |
@@ -55,3 +55,48 @@ func TestNewListSecretsFilters(t *testing.T) { |
55 | 55 |
} |
56 | 56 |
} |
57 | 57 |
} |
58 |
+ |
|
59 |
+func TestNewListConfigsFilters(t *testing.T) { |
|
60 |
+ validNameFilter := filters.NewArgs() |
|
61 |
+ validNameFilter.Add("name", "test_name") |
|
62 |
+ |
|
63 |
+ validIDFilter := filters.NewArgs() |
|
64 |
+ validIDFilter.Add("id", "7c9009d6720f6de3b492f5") |
|
65 |
+ |
|
66 |
+ validLabelFilter := filters.NewArgs() |
|
67 |
+ validLabelFilter.Add("label", "type=test") |
|
68 |
+ validLabelFilter.Add("label", "storage=ssd") |
|
69 |
+ validLabelFilter.Add("label", "memory") |
|
70 |
+ |
|
71 |
+ validAllFilter := filters.NewArgs() |
|
72 |
+ validAllFilter.Add("name", "nodeName") |
|
73 |
+ validAllFilter.Add("id", "7c9009d6720f6de3b492f5") |
|
74 |
+ validAllFilter.Add("label", "type=test") |
|
75 |
+ validAllFilter.Add("label", "memory") |
|
76 |
+ |
|
77 |
+ validFilters := []filters.Args{ |
|
78 |
+ validNameFilter, |
|
79 |
+ validIDFilter, |
|
80 |
+ validLabelFilter, |
|
81 |
+ validAllFilter, |
|
82 |
+ } |
|
83 |
+ |
|
84 |
+ invalidTypeFilter := filters.NewArgs() |
|
85 |
+ invalidTypeFilter.Add("nonexist", "aaaa") |
|
86 |
+ |
|
87 |
+ invalidFilters := []filters.Args{ |
|
88 |
+ invalidTypeFilter, |
|
89 |
+ } |
|
90 |
+ |
|
91 |
+ for _, filter := range validFilters { |
|
92 |
+ if _, err := newListConfigsFilters(filter); err != nil { |
|
93 |
+ t.Fatalf("Should get no error, got %v", err) |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ for _, filter := range invalidFilters { |
|
98 |
+ if _, err := newListConfigsFilters(filter); err == nil { |
|
99 |
+ t.Fatalf("Should get an error for filter %s, while got nil", filter) |
|
100 |
+ } |
|
101 |
+ } |
|
102 |
+} |
... | ... |
@@ -174,6 +174,42 @@ func getSecret(ctx context.Context, c swarmapi.ControlClient, input string) (*sw |
174 | 174 |
return rl.Secrets[0], nil |
175 | 175 |
} |
176 | 176 |
|
177 |
+func getConfig(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Config, error) { |
|
178 |
+ // attempt to lookup config by full ID |
|
179 |
+ if rg, err := c.GetConfig(ctx, &swarmapi.GetConfigRequest{ConfigID: input}); err == nil { |
|
180 |
+ return rg.Config, nil |
|
181 |
+ } |
|
182 |
+ |
|
183 |
+ // If any error (including NotFound), ListConfigs to match via full name. |
|
184 |
+ rl, err := c.ListConfigs(ctx, &swarmapi.ListConfigsRequest{ |
|
185 |
+ Filters: &swarmapi.ListConfigsRequest_Filters{ |
|
186 |
+ Names: []string{input}, |
|
187 |
+ }, |
|
188 |
+ }) |
|
189 |
+ if err != nil || len(rl.Configs) == 0 { |
|
190 |
+ // If any error or 0 result, ListConfigs to match via ID prefix. |
|
191 |
+ rl, err = c.ListConfigs(ctx, &swarmapi.ListConfigsRequest{ |
|
192 |
+ Filters: &swarmapi.ListConfigsRequest_Filters{ |
|
193 |
+ IDPrefixes: []string{input}, |
|
194 |
+ }, |
|
195 |
+ }) |
|
196 |
+ } |
|
197 |
+ if err != nil { |
|
198 |
+ return nil, err |
|
199 |
+ } |
|
200 |
+ |
|
201 |
+ if len(rl.Configs) == 0 { |
|
202 |
+ err := fmt.Errorf("config %s not found", input) |
|
203 |
+ return nil, errors.NewRequestNotFoundError(err) |
|
204 |
+ } |
|
205 |
+ |
|
206 |
+ if l := len(rl.Configs); l > 1 { |
|
207 |
+ return nil, fmt.Errorf("config %s is ambiguous (%d matches found)", input, l) |
|
208 |
+ } |
|
209 |
+ |
|
210 |
+ return rl.Configs[0], nil |
|
211 |
+} |
|
212 |
+ |
|
177 | 213 |
func getNetwork(ctx context.Context, c swarmapi.ControlClient, input string) (*swarmapi.Network, error) { |
178 | 214 |
// GetNetwork to match via full ID. |
179 | 215 |
if rg, err := c.GetNetwork(ctx, &swarmapi.GetNetworkRequest{NetworkID: input}); err == nil { |