move command struct in separate file
This commit is contained in:
		
							parent
							
								
									b9d594ea37
								
							
						
					
					
						commit
						5330eac174
					
				
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ build: lint | |||||||
| 	@echo "building binary..." | 	@echo "building binary..." | ||||||
| 	@go build -o $(BIN_NAME) main.go && echo "$(BIN_NAME) built" | 	@go build -o $(BIN_NAME) main.go && echo "$(BIN_NAME) built" | ||||||
| 
 | 
 | ||||||
| install: lint | install: | ||||||
| 	@$(shell whereis $(BIN_NAME) | cut -d ' ' -f2 | xargs rm -f) | 	@$(shell whereis $(BIN_NAME) | cut -d ' ' -f2 | xargs rm -f) | ||||||
| 	@go install | 	@go install | ||||||
| 	@echo "program installed: $(GOPATH)/bin/hmdeploy" | 	@echo "program installed: $(GOPATH)/bin/hmdeploy" | ||||||
|  | |||||||
							
								
								
									
										237
									
								
								docker/client.go
									
									
									
									
									
								
							
							
						
						
									
										237
									
								
								docker/client.go
									
									
									
									
									
								
							| @ -1,18 +1,15 @@ | |||||||
| package docker | package docker | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"slices" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
| 
 | 
 | ||||||
| 	"gitea.thegux.fr/hmdeploy/connection" | 	"gitea.thegux.fr/hmdeploy/connection" | ||||||
| 	"gitea.thegux.fr/hmdeploy/models" | 	"gitea.thegux.fr/hmdeploy/models" | ||||||
| 	"gitea.thegux.fr/hmdeploy/utils" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| @ -30,204 +27,19 @@ var ( | |||||||
| 	ErrDockerClientExtractServicesParse       = errors.New("parse error") | 	ErrDockerClientExtractServicesParse       = errors.New("parse error") | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Port struct { | func parseIDs(cmdOutput string) []string { | ||||||
| 	In  int | 	ids := []string{} | ||||||
| 	Out int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Service struct { |  | ||||||
| 	App   string |  | ||||||
| 	Name  string |  | ||||||
| 	Image struct { |  | ||||||
| 		Name string |  | ||||||
| 		Tag  string |  | ||||||
| 	} |  | ||||||
| 	Ports    []Port |  | ||||||
| 	Replicas int |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s Service) String() string { |  | ||||||
| 	return fmt.Sprintf( |  | ||||||
| 		"%-20s | %-20s | %-30s | %-10s | %-5d", |  | ||||||
| 		s.App, |  | ||||||
| 		s.Name, |  | ||||||
| 		s.Image.Name, |  | ||||||
| 		s.Image.Tag, |  | ||||||
| 		s.Replicas, |  | ||||||
| 	) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type Services []Service |  | ||||||
| 
 |  | ||||||
| func (s Services) String() string { |  | ||||||
| 	data := []string{} |  | ||||||
| 	header := fmt.Sprintf( |  | ||||||
| 		"%-20s | %-20s | %-30s | %-10s | %-5s", |  | ||||||
| 		"Service name", |  | ||||||
| 		"App name", |  | ||||||
| 		"Image name", |  | ||||||
| 		"Image Tag", |  | ||||||
| 		"Replicas", |  | ||||||
| 	) |  | ||||||
| 	hr := "" |  | ||||||
| 	for i := 0; i < len(header); i++ { |  | ||||||
| 		hr += "-" |  | ||||||
| 	} |  | ||||||
| 	data = append(data, hr) |  | ||||||
| 	data = append(data, header) |  | ||||||
| 	data = append(data, hr) |  | ||||||
| 	for idx := range s { |  | ||||||
| 		data = append(data, s[idx].String()) |  | ||||||
| 	} |  | ||||||
| 	return strings.Join(data, "\n") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type serviceDetailsHeader map[string]int |  | ||||||
| 
 |  | ||||||
| func (h serviceDetailsHeader) getPos(value string) int { |  | ||||||
| 	v, ok := h[value] |  | ||||||
| 	if !ok { |  | ||||||
| 		return -1 |  | ||||||
| 	} |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // serviceFromString instantiates a new service from the `docker service ls` command output. |  | ||||||
| // TODO: it's ugly... To rework, if i'm not too lazy |  | ||||||
| func serviceFromString( //nolint:all // todo |  | ||||||
| 	header serviceDetailsHeader, |  | ||||||
| 	value string, |  | ||||||
| ) (Service, error) { |  | ||||||
| 	var srv Service |  | ||||||
| 
 |  | ||||||
| 	parts := strings.Split(value, " ") |  | ||||||
| 	parts = slices.DeleteFunc(parts, func(e string) bool { return e == "" }) |  | ||||||
| 
 |  | ||||||
| 	if pos := header.getPos("name"); pos != -1 { |  | ||||||
| 		appParts := strings.Split(parts[pos], "_") |  | ||||||
| 		if len(appParts) >= appPartsLength { |  | ||||||
| 			srv.App = appParts[len(appParts)-1] |  | ||||||
| 			srv.Name = appParts[0] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if pos := header.getPos("image"); pos != -1 { |  | ||||||
| 		imageParts := strings.Split(parts[pos], ":") |  | ||||||
| 		if len(imageParts) >= imagePartsLength { |  | ||||||
| 			srv.Image.Name = imageParts[0] |  | ||||||
| 			srv.Image.Tag = imageParts[1] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if pos := header.getPos("replicas"); pos != -1 { |  | ||||||
| 		replicasParts := strings.Split(parts[pos], "/") |  | ||||||
| 		if len(replicasParts) >= replicasPartLength { |  | ||||||
| 			replicas, err := strconv.Atoi(replicasParts[1]) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return srv, fmt.Errorf( |  | ||||||
| 					"%w, replicas conversion failed: %s", |  | ||||||
| 					ErrDockerClientExtractServicesParse, |  | ||||||
| 					string(replicasParts[1]), |  | ||||||
| 				) |  | ||||||
| 			} |  | ||||||
| 			srv.Replicas = replicas |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	srv.Ports = []Port{} |  | ||||||
| 
 |  | ||||||
| 	if pos := header.getPos("ports"); pos != -1 && pos < len(parts) { |  | ||||||
| 		// ports parts must exceed the header pos so, to find missing ports |  | ||||||
| 		// the pos difference is computed in order to retrieve them |  | ||||||
| 		portsParts := []string{} |  | ||||||
| 		diff := len(parts) - pos |  | ||||||
| 		for i := 0; i < diff; i++ { |  | ||||||
| 			portsParts = append(portsParts, parts[pos+i]) |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		for _, port := range portsParts { |  | ||||||
| 			servicePort := Port{} |  | ||||||
| 			// port in/out are displayed like "out->in" |  | ||||||
| 			portParts := strings.Split(port, "->") |  | ||||||
| 			if len(portParts) >= portPartsLength { |  | ||||||
| 				// out port is displayed like "ip:port" |  | ||||||
| 				portOutParts := strings.Split(portParts[0], ":") |  | ||||||
| 				if len(portOutParts) == portOutPartsLength { |  | ||||||
| 					portOut, err := utils.CatchAndConvertNumber(portOutParts[1]) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return srv, fmt.Errorf( |  | ||||||
| 							"%w, out port conversion failed: %s", |  | ||||||
| 							ErrDockerClientExtractServicesParse, |  | ||||||
| 							string(portOutParts[1]), |  | ||||||
| 						) |  | ||||||
| 					} |  | ||||||
| 					servicePort.Out = portOut |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				portIn, err := utils.CatchAndConvertNumber(portParts[1]) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return srv, fmt.Errorf( |  | ||||||
| 						"%w, in port conversion failed: %s", |  | ||||||
| 						ErrDockerClientExtractServicesParse, |  | ||||||
| 						string(portParts[1]), |  | ||||||
| 					) |  | ||||||
| 				} |  | ||||||
| 				servicePort.In = portIn |  | ||||||
| 
 |  | ||||||
| 				srv.Ports = append(srv.Ports, servicePort) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return srv, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Parse the `docker service ls` header (ID NAME MODE REPLICAS IMAGE PORTS) |  | ||||||
| func parseServiceDetailsHeader(value string) serviceDetailsHeader { |  | ||||||
| 	header := serviceDetailsHeader{} |  | ||||||
| 
 |  | ||||||
| 	parts := strings.Split(value, " ") |  | ||||||
| 	parts = slices.DeleteFunc(parts, func(e string) bool { return e == "" }) |  | ||||||
| 
 |  | ||||||
| 	for idx, part := range parts { |  | ||||||
| 		header[part] = idx |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return header |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func parseServiceDetails(cmdOutput string) (Services, error) { |  | ||||||
| 	services := Services{} |  | ||||||
| 	var header serviceDetailsHeader |  | ||||||
| 
 |  | ||||||
| 	bufLine := []rune{} | 	bufLine := []rune{} | ||||||
| 	isHeaderCatched := false |  | ||||||
| 	for _, c := range cmdOutput { | 	for _, c := range cmdOutput { | ||||||
| 		if c == '\n' { | 		if c == '\n' { | ||||||
| 			if !isHeaderCatched { | 			ids = append(ids, string(bufLine)) | ||||||
| 				header = parseServiceDetailsHeader(strings.ToLower(string(bufLine))) |  | ||||||
| 				bufLine = bufLine[:0] |  | ||||||
| 				isHeaderCatched = true |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			service, err := serviceFromString(header, string(bufLine)) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return services, fmt.Errorf( |  | ||||||
| 					"%w, err=%v", |  | ||||||
| 					ErrDockerClientExtractServicesParse, |  | ||||||
| 					err, |  | ||||||
| 				) |  | ||||||
| 			} |  | ||||||
| 			services = append(services, service) |  | ||||||
| 			bufLine = bufLine[:0] | 			bufLine = bufLine[:0] | ||||||
| 
 | 
 | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		bufLine = append(bufLine, c) | 		bufLine = append(bufLine, c) | ||||||
| 	} | 	} | ||||||
| 
 | 	return ids | ||||||
| 	return services, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type IClient interface { | type IClient interface { | ||||||
| @ -301,16 +113,49 @@ func NewRemoteClient(netInfo *models.HMNetInfo) (RemoteClient, error) { | |||||||
| 	return rc, nil | 	return rc, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *RemoteClient) ExtractServicesDetails() (Services, error) { | func (c *RemoteClient) getIDS() ([]string, error) { | ||||||
| 	output, err := c.conn.Execute("docker service ls") | 	output, err := c.conn.Execute("docker service ls -q") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	services, err := parseServiceDetails(output) | 	return parseIDs(output), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *RemoteClient) getServiceDetails(id string) (Service, error) { | ||||||
|  | 	output, err := c.conn.Execute( | ||||||
|  | 		fmt.Sprintf( | ||||||
|  | 			`echo "{\"services\": [$(echo $(cmd=$(docker service ps %s --format json); cmd=$(echo $cmd | tr '} {' '},{'); echo $cmd))], \"details\": $(docker service inspect %s --format=json)}"`, | ||||||
|  | 			id, | ||||||
|  | 			id, | ||||||
|  | 		), | ||||||
|  | 	) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return Service{}, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sc := Service{} | ||||||
|  | 	if err := json.Unmarshal([]byte(output), &sc); err != nil { | ||||||
|  | 		return sc, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return sc, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (c *RemoteClient) ExtractServicesDetails() (Services, error) { | ||||||
|  | 	ids, err := c.getIDS() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	services := Services{} | ||||||
|  | 	for _, id := range ids { | ||||||
|  | 		srv, err := c.getServiceDetails(id) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		services = append(services, srv) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return services, nil | 	return services, nil | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										268
									
								
								docker/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								docker/models.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | |||||||
|  | package docker | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const nbImageParts = 2 | ||||||
|  | 
 | ||||||
|  | type ServiceStatus string | ||||||
|  | 
 | ||||||
|  | // docker engine service status | ||||||
|  | const ( | ||||||
|  | 	Pending    ServiceStatus = "pending" | ||||||
|  | 	Restarting ServiceStatus = "preparing" | ||||||
|  | 	Starting                 = "starting" | ||||||
|  | 	Running                  = "running" | ||||||
|  | 	Failed                   = "failed" | ||||||
|  | 	Shutdown                 = "shutdown" | ||||||
|  | 	Rejected                 = "rejected" | ||||||
|  | 	Orphaned                 = "orphaned" | ||||||
|  | 	Complete                 = "complete" | ||||||
|  | 	New                      = "new" | ||||||
|  | 	Unknown                  = "unknown" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var mapServiceStatus = map[string]ServiceStatus{ | ||||||
|  | 	"pending":    Pending, | ||||||
|  | 	"restarting": Restarting, | ||||||
|  | 	"starting":   Starting, | ||||||
|  | 	"running":    Running, | ||||||
|  | 	"failed":     Failed, | ||||||
|  | 	"shutdown":   Shutdown, | ||||||
|  | 	"rejected":   Rejected, | ||||||
|  | 	"orphaned":   Orphaned, | ||||||
|  | 	"complete":   Complete, | ||||||
|  | 	"new":        New, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func serviceStatusFromString(value string) ServiceStatus { | ||||||
|  | 	if v, ok := mapServiceStatus[strings.ToLower(value)]; ok { | ||||||
|  | 		return v | ||||||
|  | 	} | ||||||
|  | 	return Unknown | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getReplicas(states []serviceState, nbReplicas int) []Replicas { | ||||||
|  | 	replicas := []Replicas{} | ||||||
|  | 
 | ||||||
|  | 	// collect latest services for each replicas | ||||||
|  | 	rep := 1 | ||||||
|  | 	for idx := range states { | ||||||
|  | 		if rep > nbReplicas { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if states[idx].ReplicasNumber == rep { | ||||||
|  | 			replicas = append(replicas, Replicas{ | ||||||
|  | 				Pos:   rep, | ||||||
|  | 				State: serviceStatusFromString(states[idx].Current), | ||||||
|  | 				Error: states[idx].Error, | ||||||
|  | 			}) | ||||||
|  | 			rep++ | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return replicas | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Replicas struct { | ||||||
|  | 	State ServiceStatus | ||||||
|  | 	Error string | ||||||
|  | 	Pos   int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ServicePort struct { | ||||||
|  | 	Target    int `json:"TargetPort"` | ||||||
|  | 	Published int `json:"PublishedPort"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type serviceCommandInput struct { | ||||||
|  | 	States  []serviceState   `json:"services"` | ||||||
|  | 	Details []serviceDetails `json:"details"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Service struct { | ||||||
|  | 	ID    string | ||||||
|  | 	App   string | ||||||
|  | 	Name  string | ||||||
|  | 	Image struct { | ||||||
|  | 		Name string | ||||||
|  | 		Tag  string | ||||||
|  | 	} | ||||||
|  | 	Replicas []Replicas | ||||||
|  | 	Ports    []ServicePort | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *Service) UnmarshalJSON(data []byte) error { | ||||||
|  | 	ci := serviceCommandInput{} | ||||||
|  | 	if err := json.Unmarshal(data, &ci); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(ci.Details) == 0 { | ||||||
|  | 		return fmt.Errorf("service details must no be empty") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nbReplicas := ci.Details[0].Spec.Mode.Replicated.Replicas | ||||||
|  | 	if len(ci.States) < nbReplicas { | ||||||
|  | 		return fmt.Errorf("must have %d replicas but have %d", nbReplicas, len(ci.States)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	replicas := getReplicas(ci.States, nbReplicas) | ||||||
|  | 
 | ||||||
|  | 	imageName := ci.Details[0].Spec.Labels.Image | ||||||
|  | 	imageNameParts := strings.Split(imageName, ":") | ||||||
|  | 	if len(imageNameParts) == nbImageParts { | ||||||
|  | 		imageName = imageNameParts[0] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	appName := ci.Details[0].Spec.Labels.Namespace | ||||||
|  | 
 | ||||||
|  | 	*s = Service{ | ||||||
|  | 		ID:   ci.Details[0].ID, | ||||||
|  | 		App:  appName, | ||||||
|  | 		Name: strings.TrimPrefix(ci.Details[0].Spec.Name, appName+"_"), | ||||||
|  | 		Image: struct { | ||||||
|  | 			Name string | ||||||
|  | 			Tag  string | ||||||
|  | 		}{ | ||||||
|  | 			Name: imageName, | ||||||
|  | 			Tag:  ci.Details[0].Spec.Labels.Tag, | ||||||
|  | 		}, | ||||||
|  | 		Ports:    ci.Details[0].Endpoint.Ports, | ||||||
|  | 		Replicas: replicas, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s Service) String() string { | ||||||
|  | 	data := []string{} | ||||||
|  | 	fl := fmt.Sprintf( | ||||||
|  | 		"%-20s | %-20s | %-30s | %-10s | %-5d", | ||||||
|  | 		s.App, | ||||||
|  | 		s.Name, | ||||||
|  | 		s.Image.Name, | ||||||
|  | 		s.Image.Tag, | ||||||
|  | 		len(s.Replicas), | ||||||
|  | 	) | ||||||
|  | 	data = append(data, fl) | ||||||
|  | 
 | ||||||
|  | 	for idx := range s.Replicas { | ||||||
|  | 		data = append(data, fmt.Sprintf( | ||||||
|  | 			"%-20s \\ %-20d | %-30s | %-30s", | ||||||
|  | 			"", | ||||||
|  | 			s.Replicas[idx].Pos, | ||||||
|  | 			s.Replicas[idx].State, | ||||||
|  | 			s.Replicas[idx].Error, | ||||||
|  | 		)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return strings.Join(data, "\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type Services []Service | ||||||
|  | 
 | ||||||
|  | func (s Services) GetIDs() []string { | ||||||
|  | 	ids := []string{} | ||||||
|  | 	for idx := range s { | ||||||
|  | 		ids = append(ids, s[idx].ID) | ||||||
|  | 	} | ||||||
|  | 	return ids | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s Services) String() string { | ||||||
|  | 	data := []string{} | ||||||
|  | 	header := fmt.Sprintf( | ||||||
|  | 		"%-20s | %-20s | %-30s | %-10s | %-5s", | ||||||
|  | 		"Service name", | ||||||
|  | 		"Name", | ||||||
|  | 		"Image name", | ||||||
|  | 		"Image Tag", | ||||||
|  | 		"Replicas", | ||||||
|  | 	) | ||||||
|  | 	subheader := fmt.Sprintf( | ||||||
|  | 		"%-20s \\ %-20s | %-30s | %-30s", | ||||||
|  | 		"", | ||||||
|  | 		"Replicas", | ||||||
|  | 		"Status", | ||||||
|  | 		"Error", | ||||||
|  | 	) | ||||||
|  | 	hr := "" | ||||||
|  | 	for i := 0; i < len(header); i++ { | ||||||
|  | 		hr += "*" | ||||||
|  | 	} | ||||||
|  | 	subhr := "" | ||||||
|  | 	for i := 0; i < len(header); i++ { | ||||||
|  | 		subhr += "-" | ||||||
|  | 	} | ||||||
|  | 	data = append(data, hr) | ||||||
|  | 	data = append(data, header) | ||||||
|  | 	data = append(data, subheader) | ||||||
|  | 	data = append(data, hr) | ||||||
|  | 	for idx := range s { | ||||||
|  | 		data = append(data, s[idx].String()) | ||||||
|  | 		data = append(data, subhr) | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(data, "\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type serviceDetails struct { | ||||||
|  | 	ID        string    `json:"ID"` | ||||||
|  | 	CreateAt  time.Time `json:"CreatedAt"` | ||||||
|  | 	UpdatedAt time.Time `json:"UpdatedAt"` | ||||||
|  | 	Spec      struct { | ||||||
|  | 		Name   string `json:"Name"` | ||||||
|  | 		Labels struct { | ||||||
|  | 			Image     string `json:"com.docker.stack.image"` | ||||||
|  | 			Tag       string | ||||||
|  | 			Namespace string `json:"com.docker.stack.namespace"` | ||||||
|  | 		} `json:"Labels"` | ||||||
|  | 		Mode struct { | ||||||
|  | 			Replicated struct { | ||||||
|  | 				Replicas int `json:"Replicas"` | ||||||
|  | 			} `json:"Replicated"` | ||||||
|  | 		} `json:"Mode"` | ||||||
|  | 	} `json:"spec"` | ||||||
|  | 	Endpoint struct { | ||||||
|  | 		Ports []ServicePort `json:"Ports"` | ||||||
|  | 	} `json:"Endpoint"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type serviceStateInput serviceState | ||||||
|  | 
 | ||||||
|  | type serviceState struct { | ||||||
|  | 	Name           string `json:"Name"` | ||||||
|  | 	Current        string `json:"CurrentState"` | ||||||
|  | 	Desired        string `json:"DesiredState"` | ||||||
|  | 	Error          string `json:"Error"` | ||||||
|  | 	ReplicasNumber int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (st *serviceState) UnmarshalJSON(data []byte) error { | ||||||
|  | 	state := serviceStateInput{} | ||||||
|  | 	if err := json.Unmarshal(data, &state); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nameParts := strings.Split(state.Name, ".") | ||||||
|  | 	if len(nameParts) > 0 { | ||||||
|  | 		replicasNumber, err := strconv.Atoi(nameParts[1]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		state.ReplicasNumber = replicasNumber | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	currentStateParts := strings.Split(state.Current, ",") | ||||||
|  | 	if len(currentStateParts) > 0 { | ||||||
|  | 		state.Current = currentStateParts[0] | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	*st = (serviceState)(state) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 rmanach
						rmanach