`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>
143 lines
4.1 KiB
Go
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)
|
|
}
|