Files
docker-cli/cli/command/stack/kubernetes/services.go
Mathieu Champlon 297866ebbe Match Swarm in how to combine filters
`docker stack services --filter=label=foo=bar --filter=label=foo=baz my-stack` with Swarm gets handled as `filter on (a label named foo with value bar) AND (a label named foo with value baz).
This obviously yields an empty result set every time, but if and how this should be changed is out of scope here, so simply align Kubernetes with Swarm for now.

Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
2018-05-24 15:24:23 +02:00

143 lines
4.1 KiB
Go

package kubernetes
import (
"fmt"
"sort"
"strings"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/kubernetes/labels"
"github.com/docker/docker/api/types/filters"
appsv1beta2 "k8s.io/api/apps/v1beta2"
corev1 "k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var supportedServicesFilters = map[string]bool{
"mode": true,
"name": true,
"label": true,
}
func generateSelector(labels map[string][]string) []string {
var result []string
for k, v := range labels {
for _, val := range v {
result = append(result, fmt.Sprintf("%s=%s", k, val))
}
if len(v) == 0 {
result = append(result, k)
}
}
return result
}
func parseLabelFilters(rawFilters []string) map[string][]string {
labels := map[string][]string{}
for _, rawLabel := range rawFilters {
v := strings.SplitN(rawLabel, "=", 2)
key := v[0]
if len(v) > 1 {
labels[key] = append(labels[key], v[1])
} else if _, ok := labels[key]; !ok {
labels[key] = []string{}
}
}
return labels
}
func generateLabelSelector(f filters.Args, stackName string) string {
names := f.Get("name")
sort.Strings(names)
for _, n := range names {
if strings.HasPrefix(n, stackName+"_") {
// also accepts with unprefixed service name (for compat with existing swarm scripts where service names are prefixed by stack names)
names = append(names, strings.TrimPrefix(n, stackName+"_"))
}
}
selectors := append(generateSelector(parseLabelFilters(f.Get("label"))), labels.SelectorForStack(stackName, names...))
return strings.Join(selectors, ",")
}
func getResourcesForServiceList(dockerCli *KubeCli, filters filters.Args, labelSelector string) (*appsv1beta2.ReplicaSetList, *appsv1beta2.DaemonSetList, *corev1.ServiceList, error) {
client, err := dockerCli.composeClient()
if err != nil {
return nil, nil, nil, err
}
modes := filters.Get("mode")
replicas := &appsv1beta2.ReplicaSetList{}
if len(modes) == 0 || filters.ExactMatch("mode", "replicated") {
if replicas, err = client.ReplicaSets().List(metav1.ListOptions{LabelSelector: labelSelector}); err != nil {
return nil, nil, nil, err
}
}
daemons := &appsv1beta2.DaemonSetList{}
if len(modes) == 0 || filters.ExactMatch("mode", "global") {
if daemons, err = client.DaemonSets().List(metav1.ListOptions{LabelSelector: labelSelector}); err != nil {
return nil, nil, nil, err
}
}
services, err := client.Services().List(metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return nil, nil, nil, err
}
return replicas, daemons, services, nil
}
// RunServices is the kubernetes implementation of docker stack services
func RunServices(dockerCli *KubeCli, opts options.Services) error {
filters := opts.Filter.Value()
if err := filters.Validate(supportedServicesFilters); err != nil {
return err
}
client, err := dockerCli.composeClient()
if err != nil {
return nil
}
stacks, err := client.Stacks(false)
if err != nil {
return nil
}
stackName := opts.Namespace
_, err = stacks.Get(stackName)
if apierrs.IsNotFound(err) {
return fmt.Errorf("nothing found in stack: %s", stackName)
}
if err != nil {
return err
}
labelSelector := generateLabelSelector(filters, stackName)
replicasList, daemonsList, servicesList, err := getResourcesForServiceList(dockerCli, filters, labelSelector)
if err != nil {
return err
}
// Convert Replicas sets and kubernetes services to swam services and formatter informations
services, info, err := convertToServices(replicasList, daemonsList, servicesList)
if err != nil {
return err
}
if opts.Quiet {
info = map[string]formatter.ServiceListInfo{}
}
format := opts.Format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
}
}
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, opts.Quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}