package dockerswarm import ( "encoding/json" "fmt" "net" "strconv" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils" "github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils" ) // See https://docs.docker.com/engine/api/v1.40/#tag/Task type task struct { ID string ServiceID string NodeID string DesiredState string NetworksAttachments []struct { Addresses []string Network struct { ID string } } Status struct { State string ContainerStatus struct { ContainerID string } PortStatus struct { Ports []portConfig } } Spec struct { ContainerSpec struct { Labels map[string]string } } Slot int } func getTasksLabels(cfg *apiConfig) ([]*promutils.Labels, error) { tasks, err := getTasks(cfg) if err != nil { return nil, err } services, err := getServices(cfg) if err != nil { return nil, err } networkLabels, err := getNetworksLabelsByNetworkID(cfg) if err != nil { return nil, err } svcLabels := addServicesLabels(services, networkLabels, cfg.port) nodeLabels, err := getNodesLabels(cfg) if err != nil { return nil, err } return addTasksLabels(tasks, nodeLabels, svcLabels, networkLabels, services, cfg.port), nil } func getTasks(cfg *apiConfig) ([]task, error) { filtersQueryArg := "" if cfg.role == "tasks" { filtersQueryArg = cfg.filtersQueryArg } resp, err := cfg.getAPIResponse("/tasks", filtersQueryArg) if err != nil { return nil, fmt.Errorf("cannot query dockerswarm api for tasks: %w", err) } return parseTasks(resp) } func parseTasks(data []byte) ([]task, error) { var tasks []task if err := json.Unmarshal(data, &tasks); err != nil { return nil, fmt.Errorf("cannot parse tasks: %w", err) } return tasks, nil } func addTasksLabels(tasks []task, nodesLabels, servicesLabels []*promutils.Labels, networksLabels map[string]*promutils.Labels, services []service, port int) []*promutils.Labels { var ms []*promutils.Labels for _, task := range tasks { commonLabels := promutils.NewLabels(8) commonLabels.Add("__meta_dockerswarm_task_id", task.ID) commonLabels.Add("__meta_dockerswarm_task_container_id", task.Status.ContainerStatus.ContainerID) commonLabels.Add("__meta_dockerswarm_task_desired_state", task.DesiredState) commonLabels.Add("__meta_dockerswarm_task_slot", strconv.Itoa(task.Slot)) commonLabels.Add("__meta_dockerswarm_task_state", task.Status.State) for k, v := range task.Spec.ContainerSpec.Labels { commonLabels.Add(discoveryutils.SanitizeLabelName("__meta_dockerswarm_container_label_"+k), v) } var svcPorts []portConfig for i, v := range services { if v.ID == task.ServiceID { svcPorts = services[i].Endpoint.Ports break } } addLabels(commonLabels, servicesLabels, "__meta_dockerswarm_service_id", task.ServiceID) addLabels(commonLabels, nodesLabels, "__meta_dockerswarm_node_id", task.NodeID) for _, port := range task.Status.PortStatus.Ports { if port.Protocol != "tcp" { continue } m := promutils.NewLabels(10) m.AddFrom(commonLabels) m.Add("__address__", discoveryutils.JoinHostPort(commonLabels.Get("__meta_dockerswarm_node_address"), port.PublishedPort)) m.Add("__meta_dockerswarm_task_port_publish_mode", port.PublishMode) // Remove possible duplicate labels, which can appear after AddFrom() call m.RemoveDuplicates() ms = append(ms, m) } for _, na := range task.NetworksAttachments { networkLabels := networksLabels[na.Network.ID] for _, address := range na.Addresses { ip, _, err := net.ParseCIDR(address) if err != nil { logger.Errorf("cannot parse task network attachments address: %s as net CIDR: %v", address, err) continue } added := false for _, ep := range svcPorts { if ep.Protocol != "tcp" { continue } m := promutils.NewLabels(20) m.AddFrom(commonLabels) m.AddFrom(networkLabels) m.Add("__address__", discoveryutils.JoinHostPort(ip.String(), ep.PublishedPort)) m.Add("__meta_dockerswarm_task_port_publish_mode", ep.PublishMode) // Remove possible duplicate labels, which can appear after AddFrom() calls m.RemoveDuplicates() ms = append(ms, m) added = true } if !added { m := promutils.NewLabels(20) m.AddFrom(commonLabels) m.AddFrom(networkLabels) m.Add("__address__", discoveryutils.JoinHostPort(ip.String(), port)) // Remove possible duplicate labels, which can appear after AddFrom() calls m.RemoveDuplicates() ms = append(ms, m) } } } } return ms } // addLabels adds labels from src to dst if they contain the given `key: value` pair. func addLabels(dst *promutils.Labels, src []*promutils.Labels, key, value string) { for _, m := range src { if m.Get(key) != value { continue } for _, label := range m.GetLabels() { dst.Add(label.Name, label.Value) } return } }