Verified Commit 0047e48e authored by Mario Kleinsasser's avatar Mario Kleinsasser
Browse files

Add all initial files


Signed-off-by: Mario Kleinsasser's avatarMario Kleinsasser <mario.kleinsasser@gmail.com>
parent d23c60e9
**/certs/*
!**/certs/.placeholder
**/internal/*
!**/internal/.placeholder
......@@ -178,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
......@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
bosnd itself is distributed under the Apache Version 2.0, January 2004, license. A copy of this license is included as LICENSE file in the sourcecode.
# go-winio The MIT License (MIT)
"checksumSHA1": "o/3cn04KAiwC7NqNVvmfVTD+hgA=",
"path": "github.com/Microsoft/go-winio",
"revision": "78439966b38d69bf38227fbf57ac8a6fee70f69a",
"revisionTime": "2017-08-04T20:09:54Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "Gj+xR1VgFKKmFXYOJMnAczC3Znk=",
"path": "github.com/docker/distribution/digestset",
"revision": "e5b5e44386f7964a1f010a2b8b7687d073215bbb",
"revisionTime": "2017-11-09T22:49:04Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "2Fe4D6PGaVE2he4fUeenLmhC1lE=",
"path": "github.com/docker/distribution/reference",
"revision": "e5b5e44386f7964a1f010a2b8b7687d073215bbb",
"revisionTime": "2017-11-09T22:49:04Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "s2aRO7Ze3JVu//9FeJ4FrCFahDM=",
"path": "github.com/docker/docker/api",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "HoN/78ovv3/DC+kDKF7IENEc40g=",
"path": "github.com/docker/docker/api/types",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "jVJDbe0IcyjoKc2xbohwzQr+FF0=",
"path": "github.com/docker/docker/api/types/blkiodev",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "uhgObLWZ3XZE8mdf6ovciqBgljQ=",
"path": "github.com/docker/docker/api/types/container",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "XDP7i6sMYGnUKeFzgt+mFBJwjjw=",
"path": "github.com/docker/docker/api/types/events",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "S4SWOa0XduRd8ene8Alwih2Nwcw=",
"path": "github.com/docker/docker/api/types/filters",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "yeB781yxPhnN6OXQ9/qSsyih3ek=",
"path": "github.com/docker/docker/api/types/image",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "uJeLBKpHZXP+bWhXP4HhpyUTWYI=",
"path": "github.com/docker/docker/api/types/mount",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "Gskp+nvbVe8Gk1xPLHylZvNmqTg=",
"path": "github.com/docker/docker/api/types/network",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "r2vWq7Uc3ExKzMqYgH0b4AKjLKY=",
"path": "github.com/docker/docker/api/types/registry",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "VTxWyFud/RedrpllGdQonVtGM/A=",
"path": "github.com/docker/docker/api/types/strslice",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "Q0U3queMsCw+rPPztXnRHwAxQEc=",
"path": "github.com/docker/docker/api/types/swarm",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "mi8EDCDjtrZEONRXPG7VHJosDwY=",
"path": "github.com/docker/docker/api/types/swarm/runtime",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "77axKFOjRx1nGrzIggGXfTxUYVQ=",
"path": "github.com/docker/docker/api/types/time",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "uDPQ3nHsrvGQc9tg/J9OSC4N5dQ=",
"path": "github.com/docker/docker/api/types/versions",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "IBJy2zPEnYmcFJ3lM1eiRWnCxTA=",
"path": "github.com/docker/docker/api/types/volume",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "yeXlQUvoOI9OE0DmBAcL3NBNLF8=",
"path": "github.com/docker/docker/client",
"revision": "97be2a075225945ca7b92d75f85048cfd05a3a71",
"revisionTime": "2017-11-10T15:26:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "JbiWTzH699Sqz25XmDlsARpMN9w=",
"path": "github.com/docker/go-connections/nat",
"revision": "3ede32e2033de7505e6500d6c868c2b9ed9f169d",
"revisionTime": "2017-06-23T20:36:43Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "jUfDG3VQsA2UZHvvIXncgiddpYA=",
"path": "github.com/docker/go-connections/sockets",
"revision": "3ede32e2033de7505e6500d6c868c2b9ed9f169d",
"revisionTime": "2017-06-23T20:36:43Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "c6lDGNwTm5mYq18IHP+lqYpk8xU=",
"path": "github.com/docker/go-connections/tlsconfig",
"revision": "3ede32e2033de7505e6500d6c868c2b9ed9f169d",
"revisionTime": "2017-06-23T20:36:43Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "UmXGieuTJQOzJPspPJTVKKKMiUA=",
"path": "github.com/docker/go-units",
"revision": "0dadbb0345b35ec7ef35e228dabb8de89a65bf52",
"revisionTime": "2017-01-27T09:51:30Z"
# Copyright (c) 2013, The GoGo Authors. All rights reserved.
"checksumSHA1": "wn2shNJMwRZpvuvkf1s7h0wvqHI=",
"path": "github.com/gogo/protobuf/proto",
"revision": "616a82ed12d78d24d4839363e8f3c5d3f20627cf",
"revisionTime": "2017-11-09T18:15:19Z"
# DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004
"checksumSHA1": "uopGHAunQn3o9PC8yAfOfrmdn10=",
"path": "github.com/logrusorgru/aurora",
"revision": "b9c3722b64a1976e0624448ee7f5a0744b8cf629",
"revisionTime": "2017-08-18T11:33:00Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "OFNit1Qx2DdWhotfREKodDNUwCM=",
"path": "github.com/opencontainers/go-digest",
"revision": "279bed98673dd5bef374d3b6e4b09e2af76183bf",
"revisionTime": "2017-06-07T19:53:33Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "ZGlIwSRjdLYCUII7JLE++N4w7Xc=",
"path": "github.com/opencontainers/image-spec/specs-go",
"revision": "89b51c794e9113108a2914e38e66c826a649f2b5",
"revisionTime": "2017-11-03T11:36:04Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "jdbXRRzeu0njLE9/nCEZG+Yg/Jk=",
"path": "github.com/opencontainers/image-spec/specs-go/v1",
"revision": "89b51c794e9113108a2914e38e66c826a649f2b5",
"revisionTime": "2017-11-03T11:36:04Z"
# Copyright (c) 2015, Dave Cheney <dave@cheney.net>
"checksumSHA1": "rJab1YdNhQooDiBWNnt7TLWPyBU=",
"path": "github.com/pkg/errors",
"revision": "f15c970de5b76fac0b59abb32d62c17cc7bed265",
"revisionTime": "2017-10-18T19:55:50Z"
#The MIT License (MIT)
"checksumSHA1": "BYvROBsiyAXK4sq6yhDe8RgT4LM=",
"path": "github.com/sirupsen/logrus",
"revision": "89742aefa4b206dcf400792f3bd35b542998eb3b",
"revisionTime": "2017-08-22T13:27:46Z"
# Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "5Yb2z6UO+Arm/TEd+OEtdnwOt1A=",
"path": "golang.org/x/crypto/ssh/terminal",
"revision": "6a293f2d4b14b8e6d3f0539e383f6d0d30fce3fd",
"revisionTime": "2017-09-25T11:22:06Z"
Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=",
"path": "golang.org/x/net/context",
"revision": "a337091b0525af65de94df2eb7e98bd9962dcbe2",
"revisionTime": "2016-10-09T20:39:03Z"
Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=",
"path": "golang.org/x/net/context/ctxhttp",
"revision": "a337091b0525af65de94df2eb7e98bd9962dcbe2",
"revisionTime": "2016-10-09T20:39:03Z"
Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "r9l4r3H6FOLQ0c2JaoXpopFjpnw=",
"path": "golang.org/x/net/proxy",
"revision": "a337091b0525af65de94df2eb7e98bd9962dcbe2",
"revisionTime": "2016-10-09T20:39:03Z"
Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "r1jWq0V3AI5DLN0aCnXXMH/is9Q=",
"path": "golang.org/x/sys/unix",
"revision": "1e2299c37cc91a509f1b12369872d27be0ce98a6",
"revisionTime": "2017-11-09T13:50:42Z"
Copyright (c) 2009 The Go Authors. All rights reserved.
"checksumSHA1": "ck5uxoEeMDUL/QqPvGvBmcbsJzg=",
"path": "golang.org/x/sys/windows",
"revision": "1e2299c37cc91a509f1b12369872d27be0ce98a6",
"revisionTime": "2017-11-09T13:50:42Z"
# Apache Version 2.0, January 2004
"checksumSHA1": "RDJpJQwkF012L6m/2BJizyOksNw=",
"path": "gopkg.in/yaml.v2",
"revision": "eb3733d160e74a9c7e442f435eb3bea458e1d19f",
"revisionTime": "2017-08-12T16:00:11Z"
SHELL := /bin/bash
GOPATH := /home/${USER}/git/go
export GOPATH
# The name of the executable (default is current directory name)
TARGET := $(shell echo $${PWD\#\#*/})
.DEFAULT_GOAL: $(TARGET)
# These will be provided to the target
VERSION := 1.0.0
BUILD := `git rev-parse HEAD`
# Use linker flags to provide version/build settings to the target
LDFLAGS=-ldflags "-X=main.Version=$(VERSION) -X=main.Build=$(BUILD)"
# go source files, ignore vendor directory
SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*")
.PHONY: build
all: build
$(TARGET): $(SRC)
@go build $(LDFLAGS) -o $(TARGET)
build: $(TARGET)
@true
# bosnd
bosnd, the boatswain daemon. Dynamic, Docker Swarm based configuration file templating for all kind of services (Apache httpd, Nginx, HAProxy, ...) made real.
bosnd, the boatswain daemon. Dynamic, Docker Swarm based configuration file templating for all kind of services (Apache httpd, Nginx, HAProxy, ...) made real. ```bosnd``` means boatswain daemon. The word itself cames from ```bos'n``` which is a pronounciation used by people with sea legs.
## What it does
```bosnd``` takes a configuration file as argument and based on the configuration file, it uses the given Docker Swarm connection to retrieve information from the Docker Swarm. Therefore the daemon connects to the Docker Swarm manger (leader or promoted node) Docker API. The information needed is collected from the ```docker network inspect -v <network>``` command. After the information is retrieved, ```bosnd``` processes the configured Golang templates and write the resulting configuration files to the disired location. Afterwards ```bosnd``` will reload the controlled daemon, which is also configured in the ```bosnd.yml``` config file.
## Why don't use xyz...
```bosnd``` is not meant to replace an already existing software. If you are familar with Traefik, please use it. We have created ```bosnd``` because we were in the need to configure software in the Docker environment in a dynamic way. For example, Traefik is already able to read the running services out of the Docker Swarm events. But for us, thats not all we need and also thats not enough. Imagine the following situation: You have a myriad of Apache Tomcat application servers as backends and they are working perfect in combination with Apache httpd as loadbalancer because the Apache httpd has a very pretty module called ```mod_jk```. Now, if you want to modernize this applications with the Docker Swarm environment, you will face some problems. First, you wont drop Apache httpd away, because it does to job best. Therefore you need a flexible way to change the configuration of the httpd on the fly and reload it. Also, you won't like to map the Docker socket in the loadbalancer to read the Docker events. That puts the system on risk. If something terrible happens, someone can have access to the full Docker Swarm API. Bad. It's better to commuicate with the swarm via the external API, the API can be protected by RBAC mechanism like [Golang casbin](https://github.com/casbin/casbin-authz-plugin). An last but not least, you may have more than one service like the Apache httpd which you would like to empower with dynamic template based configuration.
## And what about confd?
As we wrote ```bosnd``` we soon recognized, that we are writing something like [confd](https://github.com/kelseyhightower/confd). Thats true. But with a different approach. confd will propagate the affected daemon with the new configuration and the reload/restart it. ```bosnd``` in the opposite will be the process number one inside the container, the process with the number 1. He is the responsible for the invoked daemon. If ```bosnd``` dies, the container dies and it will be restarted. Yes, there is a lot of discussion about one ore more processes inside a container but this is the desicion we made for us because we need a general purpose tool. Next, instead of forcing the labels you can use like Traefik does (no critic here), we decide to let you label things you like it because, instead of Traefik we can use the full capability of the Golang Template language.
## But it is not real time...
... you are not listening on the Docker events!!!
Please correctly define real time! Real time in computing is, when you can gurantee a system respone whitin a specific time frame. By default, ```bosnd``` will pull the acutal state of the Docker network every 30 seconds. This is real time.
## Examples
You can find various run examples in the ```examples``` folder in the source code directory. But you will ever have to run a command like ```./bosnd -c <mybosndconfigfile>```. The ```bosnd``` example configuration files are located within the example directories. Most of the examples will include a ```demo.sh``` file, which will help you to get used to the idea of the ```bosnd```. The example directories will include a ```README.md``` too, read it! You can find a lot of information there.
## Template files
The template files are working with the [Golang template language](https://golang.org/pkg/text/template/).
\ No newline at end of file
File added
<
/*
Copyright 2017 Mario Kleinsasser and Bernhard Rausch
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"bufio"
"bytes"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"sort"
"strconv"
"syscall"
"text/template"
"time"
"golang.org/x/net/context"
_ "net/http/pprof"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
. "github.com/logrusorgru/aurora"
log "github.com/sirupsen/logrus"
)
var mainloop bool
var ctrlcmd *exec.Cmd
var dockerclient *client.Client
var configfile string
func isprocessrunningps(config *Config) (running bool) {
// get all folders from proc filesystem
running = false
files, _ := ioutil.ReadDir("/proc")
for _, f := range files {
// check if folder is a integer (process number)
if _, err := strconv.Atoi(f.Name()); err == nil {
// open status file of process
f, err := os.Open("/proc/" + f.Name() + "/status")
if err != nil {
log.Info(err)
return running
}
// read status line by line
scanner := bufio.NewScanner(f)
// check if process name in status of process
for scanner.Scan() {
re := regexp.MustCompile("^Name:\\s*" + config.Cmd.Processname + ".*")
match := re.MatchString(scanner.Text())
if match == true {
running = true
log.Debug("Process running: " + strconv.FormatBool(running))
}
}
if running == true {
return running
}
}
}
return running
}
func startprocess(config *Config) {
log.Info("Start Process!")
cmd := exec.Command(config.Cmd.Start[0], config.Cmd.Start[1:]...)
// Attach the process to OS stderr, OS stdout
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
// Start the process
err := cmd.Start()
if err != nil {
log.Warn(Cyan(err.Error()))
}
ctrlcmd = cmd
// just give the process some time to start
time.Sleep(time.Duration(250) * time.Millisecond)
ok := isprocessrunningps(config)
if ok == true {
log.Info(Green("Process started"))
}
}
func reloadprocess(config *Config) {
log.Info("Reloading Process!")
cmd := exec.Command(config.Cmd.Reload[0], config.Cmd.Reload[1:]...)
err := cmd.Start()
if err != nil {
log.Warn(Cyan(err.Error()))
}
cmd.Wait()
isprocessrunningps(config)
}
func writeconfig(config *Config) (changed bool) {
changed = false
log.Debug(config.Templates)
for k, v := range config.Templates {
log.Debug("Processing Template: " + k)
log.Debug("Template values: ", v)
// open template
t, err := template.ParseFiles(v.Src)
if err != nil {
log.Error(err)
continue
}
// process template
var tpl bytes.Buffer
err = t.Execute(&tpl, config.Swarm)
if err != nil {
log.Error(err)
continue
}
// create md5 of result
md5tpl := fmt.Sprintf("%x", md5.Sum([]byte(tpl.String())))
log.Debug("MD5 of CONF " + v.Src + ": " + md5tpl)
// log.Debug("TPL: " + tpl.String())
// open existing config, read it to memory
exconf, errexconf := ioutil.ReadFile(v.Dst)
if errexconf != nil {
log.Warn("Cannot read existing conf!")
log.Warn(errexconf)
}
md5exconf := fmt.Sprintf("%x", md5.Sum(exconf))
log.Debug("MD5 of EXCONF" + v.Src + ": " + md5exconf)
// log.Debug("TPL: " + string(exconf[:]))
// comapre md5 and write config if needed
if md5tpl == md5exconf {
log.Info(Green("MD5 sums of " + v.Src + " equal! Nothing to do."))
continue
}
log.Info(Brown("MD5 sums of " + v.Src + " different writing new conf!"))
// overwrite existing conf
err = ioutil.WriteFile(v.Dst, []byte(tpl.String()), 0644)
if err != nil {
log.Error("Cannot write config file.")
log.Error(err)
}
changed = true
}
return changed
}
func getsericelabel(ctx context.Context, servicename string) (map[string]string, error) {
f := filters.NewArgs()
f.Add("name", servicename)
opts := types.ServiceListOptions{
Filters: f,
}
s, _ := dockerclient.ServiceList(ctx, opts)
labels := s[0].Spec.Labels
if len(labels) == 0 {
return labels, errors.New("Service " + servicename + " has no context label!")
}
return labels, nil
}
func getorrefreshdockerclient(config *Config) bool {
ok := false
for ok == false {
certpath := filepath.Clean(config.Swarm.Certificate)
cert, err := tls.LoadX509KeyPair(certpath+"/cert.pem", certpath+"/key.pem")
if err != nil {
log.Warn(err)
time.Sleep(time.Duration(config.Checkintervall) * time.Second)
continue
}
caCert, err := ioutil.ReadFile(certpath + "/ca.pem")
if err != nil {
log.Warn(err)
time.Sleep(time.Duration(config.Checkintervall) * time.Second)
continue
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
InsecureSkipVerify: true,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
timeout := time.Duration(5 * time.Second)
httpclient := &http.Client{Transport: transport, Timeout: timeout}
dockerclient, err = client.NewClient(config.Swarm.Managerurl, "", httpclient, nil)
if err != nil {
log.Warn(err)
time.Sleep(time.Duration(config.Checkintervall) * time.Second)
continue
}
ok = true
}
return ok
}
func getservicesofnet(config *Config) error {
ctx := context.Background()
version, err := dockerclient.ServerVersion(ctx)
if err != nil {
getorrefreshdockerclient(config)
return err
}
log.Debug(version)
swarmservices := Swarmservices{}
for _, netwn := range config.Swarm.Networks {
f := filters.NewArgs()
f.Add("name", netwn)
opts := types.NetworkListOptions{
Filters: f,
}
nl, err := dockerclient.NetworkList(ctx, opts)
if err != nil {
getorrefreshdockerclient(config)
return err
}
if len(nl) == 0 {
log.Warn("Given network not found: " + netwn)
continue
}
log.Debug(nl[0].ID)
n, err := dockerclient.NetworkInspect(ctx, nl[0].ID, types.NetworkInspectOptions{Verbose: true})
if err != nil {
return err
}
for k, s := range n.Services {
if k == "" {
continue
}
ms := Service{}
ms.Name = k
ms.Labels, err = getsericelabel(ctx, ms.Name)
if err != nil {
return err
}
// get ips to sclice
var tmpips []string
for _, t := range s.Tasks {
tmpips = append(tmpips, t.EndpointIP)