func SendToConn(data []byte, conn *net.TCPConn, path *big.Int) { // making variable for combining send data var ( err tree_lib.TreeError path_len_data = make([]byte, 4) msg_len_data = make([]byte, 4) path_data = path.Bytes() path_len = uint32(len(path_data)) buf = bytes.Buffer{} ) err.From = tree_lib.FROM_SEND_TO_CONN binary.LittleEndian.PutUint32(path_len_data, path_len) binary.LittleEndian.PutUint32(msg_len_data, path_len+uint32(len(data))+uint32(4)) buf.Write(msg_len_data) buf.Write(path_len_data) buf.Write(path_data) buf.Write(data) if conn != nil { _, err.Err = conn.Write(buf.Bytes()) if !err.IsNull() { tree_log.Error(err.From, fmt.Sprintf("Error sending data to path [%s]", path.String()), err.Error()) } } buf.Reset() }
// Init API node for connection to targets func API_INIT(targets ...string) bool { var err tree_lib.TreeError err.From = tree_lib.FROM_API_INIT if len(targets) == 0 { tree_log.Error(err.From, "For running API client you need to specify target node(s) to connect") return false } for _, n := range targets { node_info.ChildsNodeInfo[n], err = tree_db.GetNodeInfo(n) if !err.IsNull() { tree_log.Error(err.From, fmt.Sprintf("Unable Getting target (%s) node info from Node database, ", n), err.Error()) return false } } rand.Seed(time.Now().UnixNano()) node_info.CurrentNodeInfo = node_info.NodeInfo{ Name: fmt.Sprintf("%s|%s", API_NAME_PREFIX, tree_lib.RandomString(10)), Childs: targets, // Getting next prime number based on Unix Timestamp nanoseconds and // TODO: Think about making this in a different way Value: tree_lib.NextPrimeNumber((1 * rand.Int63n(100)) + int64(100)), } node_info.CurrentNodeValue = big.NewInt(node_info.CurrentNodeInfo.Value) // Setting node values based on child list node_info.CalculateChildParentNodeValues() // After we have child information lets connect to them node_info.ChildsConnectionUpdate() return true }
func HandleTriggerCustomEvent(e *tree_event.Event, api_cmd Command) { var ( out = &WriterCallback{BufferMaxSize: 1024} event_name = string(api_cmd.Data) err tree_lib.TreeError ev_data []byte ) err.From = tree_lib.FROM_TRIGGER_CUSTOM_EVENT out.OutCallback = func(data []byte, ended bool) { cb_cmd := api_cmd cb_cmd.Ended = ended cb_cmd.Data = data ev_data, err.Err = ffjson.Marshal(cb_cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } SendCommandCallback(e, ev_data) } defer out.End() err = custom_event.Trigger(event_name, out) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } }
func GetParentInfo(node string) (node_info.NodeInfo, tree_lib.TreeError) { var ( err tree_lib.TreeError pname string ) err.From = tree_lib.FROM_GET_PARENT_INFO err = ForEach(DB_NODE, func(key []byte, val []byte) error { n := node_info.NodeInfo{} err := ffjson.Unmarshal(val, &n) if err != nil { return err } if _, ok := tree_lib.ArrayContains(n.Childs, node); ok { pname = n.Name return errors.New("Just Err for break") } return nil }) if len(pname) == 0 { return node_info.NodeInfo{}, tree_lib.TreeError{} } // Node relations first element should be parent node return GetNodeInfo(pname) }
func HandleContainerCommand(ev *tree_event.Event, cmd Command) { var ( out = &WriterCallback{BufferMaxSize: 1024} docker_cmd = tree_docker.DockerCmd{} err tree_lib.TreeError ev_data []byte ) err.From = tree_lib.FROM_HANDLE_CONTAINER_COMMAND err.Err = ffjson.Unmarshal(cmd.Data, &docker_cmd) if !err.IsNull() { tree_log.Error(err.From, "unable to unmarshal command data as a docker command -> ", err.Error()) return } out.OutCallback = func(data []byte, ended bool) { cb_cmd := cmd cb_cmd.Ended = ended cb_cmd.Data = data ev_data, err.Err = ffjson.Marshal(cb_cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } SendCommandCallback(ev, ev_data) } defer out.End() tree_docker.ContainerCommands(&docker_cmd, out) }
func triggerInitEvent() tree_lib.TreeError { var ( err tree_lib.TreeError dock_containers []docker.APIContainers ) err.From = tree_lib.FROM_TRIGGER_INIT_EVENT dock_containers, err.Err = DockerClient.ListContainers(docker.ListContainersOptions{All: false}) if !err.IsNull() { return err } // Triggering event with currently running Docker containers inside tree_event.Trigger(&tree_event.Event{Name: tree_event.ON_DOCKER_INIT, LocalVar: dock_containers}) return err }
func HandleApiCommand(cmd_data []byte, api_out io.Writer) { var ( err tree_lib.TreeError cmd DockerCmd ) err.From = tree_lib.FROM_HANDLE_API_COMMAND err.Err = ffjson.Unmarshal(cmd_data, &cmd) if !err.IsNull() { writeOutput(api_out, true, fmt.Sprintf("--- Unable to parse Container command data: %s", string(cmd_data)), map[string]string{}) return } ContainerCommands(&cmd, api_out) fmt.Println(string(cmd_data)) }
func RestoreFromConfigDump(cmd *cobra.Command, args []string) { var ( dump_file string err tree_lib.TreeError ) err.From = tree_lib.FROM_RESTORE_FROM_CONFIG_DUMP dump_file, err.Err = cmd.Flags().GetString("file") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = tree_db.LoadFromDumpPath(dump_file) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } }
func SendDockerCommand(cmd tree_docker.DockerCmd, node string, target []string, tag []string, group []string) { var err tree_lib.TreeError if !tree_api.API_INIT(node) { fmt.Println("Unable to init api client") fmt.Println("Exiting ...") return } err.From = tree_lib.FROM_SEND_DOCKER_COMMAND var ( api_cmd = tree_api.Command{} wait = make(chan bool) ) api_cmd.ID = tree_lib.RandomString(20) api_cmd.Data, err.Err = ffjson.Marshal(cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } api_cmd.CommandType = tree_api.COMMAND_CONTAINER tree_event.ON(tree_event.ON_CHILD_CONNECTED, func(ev *tree_event.Event) { path := &tree_graph.Path{From: node, Nodes: target, Groups: group, Tags: tag} tree_api.SendCommand(&api_cmd, path, func(e *tree_event.Event, c tree_api.Command) bool { fmt.Println(string(c.Data)) fmt.Println(c.Ended) if c.Ended { return false } return true }) wait <- true }) <-wait }
func GroupPath(from_node, group_name string) (map[string]*big.Int, tree_lib.TreeError) { var ( path = make(map[string]*big.Int) err tree_lib.TreeError nodes_in_group []string ) err.From = tree_lib.FROM_GROUP_PATH nodes_in_group, err = tree_db.GetGroupNodes(group_name) if !err.IsNull() { return nil, err } for _, n := range nodes_in_group { if check_node[n] { targets = append(targets, n) path[n], err = NodePath(from_node, n) if !err.IsNull() { return nil, err } } else { fmt.Println("there is no server with name ", n) fmt.Println("ignoring server ", n) } } return path, err }
func TagPath(from_node, tag_name string) (map[string]*big.Int, tree_lib.TreeError) { var ( err tree_lib.TreeError path = make(map[string]*big.Int) nodes_by_tagname []string ) err.From = tree_lib.FROM_TAG_PATH nodes_by_tagname, err = tree_db.GetNodesByTagName(tag_name) if !err.IsNull() { return nil, err } for _, n := range nodes_by_tagname { if check_node[n] { targets = append(targets, n) path[n], err = NodePath(from_node, n) if !err.IsNull() { return nil, err } } else { fmt.Println("there is no server with name ", n) fmt.Println("ignoring server ", n) } } return path, err }
func callEvent(event *docker.APIEvents) tree_lib.TreeError { var err tree_lib.TreeError err.From = tree_lib.FROM_CALL_EVENT switch event.Status { case "start", "unpouse": { var ( dock_inspect *docker.Container ) dock_inspect, err.Err = DockerClient.InspectContainer(event.ID) if !err.IsNull() { return err } ci := ContainerInfo{InspectContainer: dock_inspect, ID: event.ID, Image: dock_inspect.Config.Image} tree_event.Trigger(&tree_event.Event{Name: tree_event.ON_DOCKER_CONTAINER_START, LocalVar: &ci}) } case "die", "kill", "pause": { // Sending only Container ID if it stopped // Sometimes Docker API not giving all info about container after stopping it tree_event.Trigger(&tree_event.Event{Name: tree_event.ON_DOCKER_CONTAINER_STOP, LocalVar: event.ID}) } case "pull", "tag": { var ( inspect *docker.Image ) inspect, err.Err = DockerClient.InspectImage(event.ID) if !err.IsNull() { return err } im := ImageInfo{ID: inspect.ID, Name: event.ID, Inspect: inspect} tree_event.Trigger(&tree_event.Event{Name: tree_event.ON_DOCKER_IMAGE_CREATE, LocalVar: &im}) } case "untag", "delete": { var ( inspect *docker.Image ) inspect, err.Err = DockerClient.InspectImage(event.ID) if !err.IsNull() { return err } im := ImageInfo{ID: inspect.ID, Name: event.ID, Inspect: inspect} tree_event.Trigger(&tree_event.Event{Name: tree_event.ON_DOCKER_IMAGE_DELETE, LocalVar: &im}) } } return err }
func TriggerFromData(data []byte) { var ( e = new(Event) err tree_lib.TreeError ) err.From = tree_lib.FROM_TRIGGER_FROM_DATA err.Err = ffjson.Unmarshal(data, e) if !err.IsNull() { tree_log.Error(log_from_event, err.Error()) return } Trigger(e) }
func SetParent(name string) bool { var err tree_lib.TreeError err.From = tree_lib.FROM_SET_PARENT node_info.ParentNodeInfo, err = tree_db.GetNodeInfo(name) if !err.IsNull() { tree_log.Error(err.From, "Getting parent node info from Node database, ", err.Error()) return false } return true }
func UpdateNodeChange(info node_info.NodeInfo) { var ( ev = &tree_event.Event{} err tree_lib.TreeError ) err.From = tree_lib.FROM_UPDATE_NODE_CHANGE ev.Data, err.Err = ffjson.Marshal(info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } path := &tree_graph.Path{From: node_info.CurrentNodeInfo.Name, Nodes: []string{"*"}} ev.Name = tree_event.ON_UPDATE_NODE_INFO tree_event.Trigger(ev) tree_event.Emit(ev, path) }
func PathFiles(conf_type string, paths []string) ([]string, tree_lib.TreeError) { var ( err_r tree_lib.TreeError names []string FileNames func(path string) (err tree_lib.TreeError) ) err_r.From = tree_lib.FROM_PATH_FILES FileNames = func(path string) (err tree_lib.TreeError) { err.From = tree_lib.FROM_PATH_FILES files_in_dir, e := ioutil.ReadDir(path) err.Err = e if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } for _, a := range files_in_dir { if !a.IsDir() { if strings.Replace(filepath.Ext(a.Name())[0:], ".", "", 1) == conf_type { names = append(names, fmt.Sprintf("%s/%s", path, a.Name())) } } else { err = FileNames(fmt.Sprintf("%s/%s", path, a.Name())) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } } return } for _, a := range paths { if string([]rune(a)[len(a)-1]) == "/" { a = a[:len(a)-1] } err_r = FileNames(a) if !err_r.IsNull() { tree_log.Error(err_r.From, err_r.Error()) return nil, err_r } } return names, err_r }
func HandleAddCustomEventHandlers(e *tree_event.Event, api_cmd Command) { var ( out = &WriterCallback{BufferMaxSize: 1024} handler_data map[string]interface{} err tree_lib.TreeError ev_data []byte ) err.From = tree_lib.FROM_ADD_CUSTOM_EVENT err.Err = ffjson.Unmarshal(api_cmd.Data, &handler_data) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } out.OutCallback = func(data []byte, ended bool) { cb_cmd := api_cmd cb_cmd.Ended = ended cb_cmd.Data = data ev_data, err.Err = ffjson.Marshal(cb_cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } SendCommandCallback(e, ev_data) } defer out.End() event_name := handler_data["name"].(string) handles_interfaces := handler_data["handlers"].([]interface{}) var ( handles_interfaces_data []byte handlers []custom_event.Handler ) handles_interfaces_data, err.Err = ffjson.Marshal(handles_interfaces) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err.Err = ffjson.Unmarshal(handles_interfaces_data, &handlers) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = custom_event.ON(event_name, handlers...) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } }
func SendEventTriggerCommand(cmd *cobra.Command, args []string) { var ( event_name string err tree_lib.TreeError node string targets []string target_groups []string target_tags []string ) err.From = tree_lib.FROM_SEND_COMMAND node, err.Err = cmd.Flags().GetString("node") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } targets, err.Err = cmd.Flags().GetStringSlice("target") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } target_groups, err.Err = cmd.Flags().GetStringSlice("group") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } target_tags, err.Err = cmd.Flags().GetStringSlice("tag") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } event_name, err.Err = cmd.Flags().GetString("event") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if !tree_api.API_INIT(node) { fmt.Println("Unable to init api client") fmt.Println("Exiting ...") return } var ( api_cmd = tree_api.Command{} wait_to_end = make(chan bool) ) api_cmd.Data = []byte(event_name) api_cmd.ID = tree_lib.RandomString(20) api_cmd.CommandType = tree_api.COMMAND_TRIGGER_EVENT tree_event.ON(tree_event.ON_CHILD_CONNECTED, func(ev *tree_event.Event) { path := &tree_graph.Path{From: node, Nodes: targets, Tags: target_tags, Groups: target_groups} tree_api.SendCommand(&api_cmd, path, func(e *tree_event.Event, c tree_api.Command) bool { fmt.Println(string(c.Data)) fmt.Println(c.Ended) // TODO: End coming faster than other messages FIX !!!! if c.Ended { return false } return true }) wait_to_end <- true }) <-wait_to_end }
func CompileConfig(cmd *cobra.Command, args []string) { var ( files []string conf_type string out_file string paths []string files_in_path []string err tree_lib.TreeError ) err.From = tree_lib.FROM_COMPILE_CONFIG files, err.Err = cmd.Flags().GetStringSlice("files") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } conf_type, err.Err = cmd.Flags().GetString("type") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } out_file, err.Err = cmd.Flags().GetString("out") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if _, err.Err = os.Stat(out_file); err.Err == nil { err.Err = os.Remove(out_file) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } } paths, err.Err = cmd.Flags().GetStringSlice("path") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if len(paths) > 0 { files_in_path, err = PathFiles(conf_type, paths) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } for _, a := range files_in_path { files = append(files, a) } } if len(files) > 0 { err = ParseFiles(conf_type, files...) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } DBFromConfig() err = tree_db.DumpDBPath(out_file) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } // Deleting database dir from console part err.Err = os.RemoveAll(tree_db.DB_DIR) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } }
func DBFromConfig() { var ( err tree_lib.TreeError prev_prime = int64(1) ) err.From = tree_lib.FROM_DB_FROM_CONFIG for n, nf := range GLOBAL_CONFIG.TreeNode { nf.Value = tree_lib.NextPrimeNumber(prev_prime) prev_prime = nf.Value err = tree_db.SetNodeInfo(n, nf) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } } // After having All nodes information now we can set related things for every node for n, _ := range GLOBAL_CONFIG.TreeNode { // Setting relations for every Node err = tree_db.SetRelations(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } // Setting Groups with node lists in Group database err = tree_db.AddNodeToHisGroups(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } // Setting Tags with node lists in Group database err = tree_db.AddNodeToHisTags(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } } // Setting Balancers for b, b_conf := range GLOBAL_CONFIG.Balancer { var b_data []byte b_data, err.Err = ffjson.Marshal(b_conf) if !err.IsNull() { tree_log.Error(err.From, "Error encoding balancer config", b, " -> ", err.Error()) continue } err = tree_db.Set(tree_db.DB_BALANCER, []byte(b), b_data) if !err.IsNull() { tree_log.Error(err.From, "Error setting balancer config", b, " -> ", err.Error()) } } // Setting Registry for r, r_conf := range GLOBAL_CONFIG.Registry { var r_data []byte r_data, err.Err = ffjson.Marshal(r_conf) if !err.IsNull() { tree_log.Error(err.From, "Error encoding registry config", r, " -> ", err.Error()) continue } err = tree_db.Set(tree_db.DB_REGISTRY, []byte(r), r_data) if !err.IsNull() { tree_log.Error(err.From, "Error setting registry config", r, " -> ", err.Error()) } } }
// Executing some commands using exec.Command functionality from Go in OS func HandleExecCommand(e *tree_event.Event, api_cmd Command) { var ( out = &WriterCallback{BufferMaxSize: 1024} cmd_str = string(api_cmd.Data) cmd_options = strings.Split(cmd_str, " ") cmd = exec.Command(cmd_options[0], cmd_options[1:]...) err tree_lib.TreeError ev_data []byte ) err.From = tree_lib.FROM_HANDLE_EXEC_COMMAND out.OutCallback = func(data []byte, ended bool) { cb_cmd := api_cmd cb_cmd.Ended = ended cb_cmd.Data = data ev_data, err.Err = ffjson.Marshal(cb_cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } SendCommandCallback(e, ev_data) } defer out.End() cmd.Stdout = out cmd.Stderr = out err.Err = cmd.Run() if !err.IsNull() { tree_log.Error(err.From, err.Error()) } }
func (bs *BalancingService) SubscribeEvents() { tree_event.ON(tree_event.ON_DOCKER_INIT, func(e *tree_event.Event) { var err tree_lib.TreeError err.From = tree_lib.FROM_SUBSCRIBE_EVENTS if e.LocalVar == nil { tree_log.Info(err.From, "Containers list is nil during INIT event") return } for _, c := range e.LocalVar.([]docker.APIContainers) { if port, ok := bs.DockerImages[c.Image]; ok { var ci *docker.Container ci, err.Err = tree_docker.DockerClient.InspectContainer(c.ID) if !err.IsNull() { continue } cont_addr := Address{IP: ci.NetworkSettings.IPAddress, Port: port} err.Err = bs.AddDestination(cont_addr) if !err.IsNull() { return } containerAddressMap[c.ID] = cont_addr } } }) tree_event.ON(tree_event.ON_DOCKER_CONTAINER_START, func(e *tree_event.Event) { var err tree_lib.TreeError if e.LocalVar == nil { tree_log.Info(log_from_balancer, "Container Info is nil during container Start event") return } ci := e.LocalVar.(*tree_docker.ContainerInfo) if port, ok := bs.DockerImages[ci.Image]; ok { cont_addr := Address{IP: ci.InspectContainer.NetworkSettings.IPAddress, Port: port} err.Err = bs.AddDestination(cont_addr) if !err.IsNull() { return } containerAddressMap[ci.ID] = cont_addr } }) tree_event.ON(tree_event.ON_DOCKER_CONTAINER_STOP, func(e *tree_event.Event) { if e.LocalVar == nil { tree_log.Info(log_from_balancer, "Container ID is nil during container Stop event") return } cont_id := e.LocalVar.(string) if cont_addr, ok := containerAddressMap[cont_id]; ok { bs.DeleteDestination(cont_addr) delete(containerAddressMap, cont_id) bs.CheckForStopEvent() } }) }
func handle_api_or_parent_connection(conn *net.TCPConn) { defer conn.Close() // Connection should be closed, after return this function var ( err tree_lib.TreeError msg_data []byte info_data []byte conn_name string is_api = false conn_node_info node_info.NodeInfo api_val *big.Int ) err.From = tree_lib.FROM_HANDLE_API_OR_PARENT_CONNECTION // Making basic handshake to check the API validation // Connected Parent receiving name of the child(current node) and checking is it valid or not // if it is valid name then parent sending his name as an answer // otherwise it sending CLOSE_CONNECTION_MARK and closing connection info_data, err.Err = ffjson.Marshal(node_info.CurrentNodeInfo) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } _, err.Err = tree_lib.SendMessage(info_data, conn) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } info_data, err = tree_lib.ReadMessage(conn) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } conn_name = string(info_data) if conn_name == CLOSE_CONNECTION_MARK { tree_log.Info(err.From, "Connection closed by parent node. Bad tree network handshake ! ", "Parent Addr: ", conn.RemoteAddr().String()) return } err.Err = ffjson.Unmarshal(info_data, &conn_node_info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if strings.Contains(conn_node_info.Name, tree_api.API_NAME_PREFIX) { api_val = big.NewInt(conn_node_info.Value) api_connections[api_val] = conn is_api = true } else { // TODO: Think about conn_node_info if we need to more checking for parent node info parentConnection = conn } if is_api { tree_event.TriggerWithData(tree_event.ON_API_CONNECTED, info_data) } else { tree_event.TriggerWithData(tree_event.ON_PARENT_CONNECTED, info_data) } // Listening parent messages for { msg_data, err = tree_lib.ReadMessage(conn) if !err.IsNull() { tree_log.Error(err.From, " reading data from -> ", conn_name, " ", err.Error()) break } // Handling message events handle_message(is_api, true, msg_data) } if is_api { api_connections[api_val] = nil delete(api_connections, api_val) tree_event.TriggerWithData(tree_event.ON_API_DISCONNECTED, info_data) } else { parentConnection = nil tree_event.TriggerWithData(tree_event.ON_PARENT_DISCONNECTED, info_data) } }
func init() { // This event will be triggered from Node, when API client will send some command to implement tree_event.ON(tree_event.ON_API_COMMAND, func(ev *tree_event.Event) { var err tree_lib.TreeError err.From = tree_lib.FROM_INIT cmd := Command{} err.Err = ffjson.Unmarshal(ev.Data, &cmd) if !err.IsNull() { tree_log.Error(err.From, "unable to unmarshal event data as a command -> ", err.Error()) return } switch cmd.CommandType { case COMMAND_EXEC: { HandleExecCommand(ev, cmd) } case COMMAND_LIST: { HandleListCommand(ev, cmd) } case COMMAND_UPDATE: { HandleUpdateCommand(ev, cmd) } case COMMAND_CONTAINER: { HandleContainerCommand(ev, cmd) } case COMMAND_ADD_CUSTOM_EVENT: { HandleAddCustomEventHandlers(ev, cmd) } case COMMAND_TRIGGER_EVENT: { HandleTriggerCustomEvent(ev, cmd) } } }) // This event will be triggered from API client when Node will give callback for specific commands tree_event.ON(tree_event.ON_API_COMMAND_CALLBACK, func(ev *tree_event.Event) { var err tree_lib.TreeError err.From = tree_lib.FROM_INIT cmd := Command{} err.Err = ffjson.Unmarshal(ev.Data, &cmd) if !err.IsNull() { tree_log.Error(err.From, "unable to unmarshal event data as a command -> ", err.Error()) return } if cb, ok := subscribed_command_callbacks[cmd.ID]; ok && cb.f != nil { if !cb.f(ev, cmd) { // TODO: Maybe we need mutex to lock deleting process delete(subscribed_command_callbacks, cmd.ID) if cb.c != nil { cb.c <- true // Ending wait chanel in send command } } } }) }
func HandleNodeCommand(cmd *cobra.Command, args []string) { var ( name string err tree_lib.TreeError ) err.From = tree_lib.FROM_HANDLE_NODE_COMMAND name, err.Err = cmd.Flags().GetString("set-name") if !err.IsNull() { tree_log.Error(err.From, err.Error()) } // If we have set-name flag then we just setting current_node in database and exiting if len(name) > 0 { tree_db.Set(tree_db.DB_RANDOM, []byte("current_node"), []byte(name)) return } daemon := false daemon, err.Err = cmd.Flags().GetBool("daemon") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if daemon { cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("%s node > %s 2>&1 &", os.Args[0], tree_log.LogFile)) err.Err = cmd.Run() if !err.IsNull() { log.Fatal(err.Err) } return } name, err.Err = cmd.Flags().GetString("name") if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if len(name) == 0 { current_node_byte, err := tree_db.Get(tree_db.DB_RANDOM, []byte("current_node")) if !err.IsNull() { tree_log.Error(err.From, "Getting current node name from Random database, ", err.Error()) return } if len(current_node_byte) == 0 { fmt.Println("Name is important for the first time run") return } } else { err = tree_node.SetCurrentNode(name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } tree_node.Start() }
func init() { var err tree_lib.TreeError err.From = tree_lib.FROM_INIT tree_db, err.Err = bolt.Open(DB_DIR, 0600, nil) if !err.IsNull() { tree_log.Error(log_from_db, " unable to open database", err.Error()) tree_db = nil os.Exit(1) // Without database we can't keep and share configurations, so program should be exited } // creating Buckets in database tree_db.Update(func(tx *bolt.Tx) (err error) { // Setting databases for _, d := range [][]byte{DB_NODE, DB_BALANCER, DB_RANDOM, DB_GROUP, DB_TAG, DB_RELATIONS, DB_REGISTRY, DB_EVENT} { _, err = tx.CreateBucketIfNotExists(d) if err != nil { return err } } return nil }) // Handling node change event tree_event.ON(tree_event.ON_UPDATE_NODE_INFO, func(e *tree_event.Event) { var ( info node_info.NodeInfo ev_info node_info.NodeInfo names []string data []byte mark bool ) err.Err = ffjson.Unmarshal(e.Data, &ev_info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } info, err = GetNodeInfo(ev_info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if len(info.Name) > 0 { err = DeleteNodeFromHisGroups(info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = DeleteNodeFromHisTags(info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } if len(ev_info.TreeIp) > 0 { info.TreeIp = ev_info.TreeIp mark = true } if ev_info.TreePort != -1 { info.TreePort = ev_info.TreePort mark = true } if len(ev_info.Childs[0]) > 0 { info.Childs = append(info.Childs, ev_info.Childs[0]) } if len(ev_info.Childs[1]) > 0 { if g, ok := tree_lib.ArrayContains(info.Childs, ev_info.Childs[1]); ok { info.Childs = info.Childs[:g+copy(info.Childs[g:], info.Childs[g+1:])] } } if len(ev_info.Groups[0]) > 0 { info.Groups = append(info.Groups, ev_info.Groups[0]) } if len(ev_info.Groups[1]) > 0 { if g, ok := tree_lib.ArrayContains(info.Groups, ev_info.Groups[1]); ok { info.Groups = info.Groups[:g+copy(info.Groups[g:], info.Groups[g+1:])] } } if len(ev_info.Tags[0]) > 0 { info.Tags = append(info.Tags, ev_info.Tags[0]) } if len(ev_info.Tags[1]) > 0 { if g, ok := tree_lib.ArrayContains(info.Tags, ev_info.Tags[1]); ok { info.Tags = info.Tags[:g+copy(info.Tags[g:], info.Tags[g+1:])] } } data, err.Err = ffjson.Marshal(info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = Set(DB_NODE, []byte(ev_info.Name), data) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } names, err = ListNodeNames() if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = AddNodeToHisGroups(info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = AddNodeToHisTags(info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } for _, n := range names { err = SetRelations(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } if node_info.CurrentNodeInfo.Name == ev_info.Name && mark { var e1 tree_event.Event e1.Name = tree_event.ON_RESTART_NODE tree_event.Trigger(&e1) } } else { err = Set(DB_NODE, []byte(ev_info.Name), e.Data) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } names, err = ListNodeNames() if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = AddNodeToHisGroups(ev_info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err = AddNodeToHisTags(ev_info.Name) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } for _, n := range names { err = SetRelations(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } if node_info.CurrentNodeInfo.Name == ev_info.Name { var e1 *tree_event.Event e1.Name = tree_event.ON_RESTART_NODE tree_event.Trigger(e1) } } }) // Closing database before program will be exited // Just in case if program exiting force or we don't want to make dead lock tree_event.ON(tree_event.ON_PROGRAM_EXIT, func(e *tree_event.Event) { CloseDB() }) }
func HandleListCommand(ev *tree_event.Event, cmd Command) { var ( info = make(map[string]node_info.NodeInfo) data []byte ev_data Command nodes []string nodes_in_group []string nodes_in_tag []string err tree_lib.TreeError infos Info ) err.From = tree_lib.FROM_HANDLE_LIST_COMMAND err.Err = ffjson.Unmarshal(ev.Data, &ev_data) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } err.Err = ffjson.Unmarshal(ev_data.Data, &infos) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } nodes = infos.Target for _, g := range infos.Group { nodes_in_group, err = tree_db.GetGroupNodes(g) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } for _, n := range nodes_in_group { nodes = append(nodes, n) } } for _, t := range infos.Tag { nodes_in_tag, err = tree_db.GetNodesByTagName(t) if !err.IsNull() { tree_log.Error(err.From, "getting Tags", err.Error()) return } for _, n := range nodes_in_tag { nodes = append(nodes, n) } } for _, n := range nodes { info[n], err = tree_db.GetNodeInfo(n) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } } cb_cmd := cmd cb_cmd.Data, err.Err = ffjson.Marshal(info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } data, err.Err = ffjson.Marshal(cb_cmd) if !err.IsNull() { tree_log.Error(err.From, err.Error()) return } SendCommandCallback(ev, data) }
func HandleUpdateCommand(ev *tree_event.Event, cmd Command) { var ( data Command info node_info.NodeInfo err tree_lib.TreeError ) err.From = tree_lib.FROM_HANDLE_UPDATE_COMMAND err.Err = ffjson.Unmarshal(ev.Data, &data) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } err.Err = ffjson.Unmarshal(data.Data, &info) if !err.IsNull() { tree_log.Error(err.From, err.Error()) } UpdateNodeChange(info) SendCommandCallback(ev, ev.Data) }
func ContainerCommands(cmd *DockerCmd, out io.Writer) { var ( err tree_lib.TreeError ) err.From = tree_lib.FROM_CONTAINER_COMMANDS switch cmd.Command { case COMMAND_DOCKER_CONTAINER_CREATE, COMMAND_DOCKER_CONTAINER_START: { var ( cont *docker.Container conf = &docker.Config{} host_conf = &docker.HostConfig{} cont_count int start_cont = false ) if cc, ok := cmd.Content["count"]; ok { cont_count, err.Err = strconv.Atoi(cc) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Invalid number given for Containers count: %s", cc), cmd.Content) return } } else { cont_count = 1 } if run_cmd, ok := cmd.Content["cmd"]; ok { conf.Cmd = []string{run_cmd} } if img, ok := cmd.Content["image"]; ok { conf.Image = img } // By default we just don't need any output conf.AttachStderr = false conf.AttachStdin = false conf.AttachStdout = false if cs, ok := cmd.Content["cpu"]; ok { var cs_int int cs_int, err.Err = strconv.Atoi(cs) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Invalid number given for CPU Shares: %s", cs), cmd.Content) return } host_conf.CPUShares = int64(cs_int) } if ram, ok := cmd.Content["ram"]; ok { var ram_int int ram_int, err.Err = strconv.Atoi(ram) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Invalid number given for RAM: %s", ram), cmd.Content) return } host_conf.Memory = int64(ram_int) } if st, ok := cmd.Content["start"]; ok { switch st { case "yes", "y", "true", "t": start_cont = true case "no", "n", "false", "f": start_cont = false } } // If we just want to start container by ID just running it and returning if cmd.Command == COMMAND_DOCKER_CONTAINER_START { if cid, ok := cmd.Content["container"]; ok { err.Err = DockerClient.StartContainer(cid, host_conf) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to start container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Started \n Container -> %s\n", cid), cmd.Content) } return } for i := 0; i < cont_count; i++ { cont, err.Err = DockerClient.CreateContainer(docker.CreateContainerOptions{ Config: conf, HostConfig: host_conf, }) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Container creation error: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Created \n ID -> %s\n Name -> %s\n", cont.ID, cont.Name), cmd.Content) if start_cont { err.Err = DockerClient.StartContainer(cont.ID, host_conf) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to start container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Started \n Name -> %s\n", cont.Name), cmd.Content) } } } case COMMAND_DOCKER_CONTAINER_PAUSE: { if cid, ok := cmd.Content["container"]; ok { err.Err = DockerClient.PauseContainer(cid) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to pause container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Paused \n Container -> %s\n", cid), cmd.Content) } } case COMMAND_DOCKER_CONTAINER_RESUME: { if cid, ok := cmd.Content["container"]; ok { err.Err = DockerClient.UnpauseContainer(cid) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to Resume container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Resumed \n Container -> %s\n", cid), cmd.Content) } } case COMMAND_DOCKER_CONTAINER_DELETE: { if cid, ok := cmd.Content["container"]; ok { DockerClient.StopContainer(cid, 0) // Stopping container if it exists err.Err = DockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: cid, Force: true}) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to Resume container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Resumed \n Container -> %s\n", cid), cmd.Content) } } case COMMAND_DOCKER_CONTAINER_STOP: { var ( stop_timeout = uint(0) st_int int ) if tm, ok := cmd.Content["timeout"]; ok { st_int, err.Err = strconv.Atoi(tm) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Invalid number given for Timeout: %s", tm), cmd.Content) return } stop_timeout = uint(st_int) } if cid, ok := cmd.Content["container"]; ok { err.Err = DockerClient.StopContainer(cid, stop_timeout) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Unable to Stop container: %s", err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Container Stopped \n Container -> %s\n", cid), cmd.Content) } } case COMMAND_DOCKER_IMAGE_PULL: { var ( registry string repository string tag string image string img_repo string registery_username string registery_email string registery_passord string registery_address string ) if r, ok := cmd.Content["registry"]; ok { registry = r } else { registry = "" } if im, ok := cmd.Content["image"]; ok { image = im } else { writeOutput(out, true, "--- Image Name is required during image pull process", cmd.Content) return } str_split := strings.Split(image, ":") if len(str_split) != 2 { writeOutput(out, true, "--- Please Specify image tag name with this format <repository>:<tag>", cmd.Content) return } repository = str_split[0] tag = str_split[1] if len(registry) > 0 { img_repo = fmt.Sprintf("%s/%s", registry, repository) } else { img_repo = repository } // Getting Registery authentication data if ru, ok := cmd.Content["registry_username"]; ok { registery_username = ru } if rp, ok := cmd.Content["registry_password"]; ok { registery_passord = rp } if rem, ok := cmd.Content["registry_email"]; ok { registery_email = rem } if raddr, ok := cmd.Content["registry_address"]; ok { registery_address = raddr } err.Err = DockerClient.PullImage(docker.PullImageOptions{ Registry: registry, Repository: img_repo, Tag: tag, OutputStream: nil, }, docker.AuthConfiguration{ Username: registery_username, Email: registery_email, Password: registery_passord, ServerAddress: registery_address, }) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Pulling image error: %s", err.Error()), cmd.Content) return } err.Err = DockerClient.TagImage(fmt.Sprintf("%s:%s", img_repo, tag), docker.TagImageOptions{ Repo: repository, Tag: tag, Force: true, }) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Error Renaming pulled image %s : %s", fmt.Sprintf("%s:%s", img_repo, tag), err.Error()), cmd.Content) DockerClient.RemoveImageExtended(fmt.Sprintf("%s:%s", img_repo, tag), docker.RemoveImageOptions{Force: true}) return } DockerClient.RemoveImage(fmt.Sprintf("%s:%s", img_repo, tag)) writeOutput(out, false, fmt.Sprintf("Image Created %s", image), cmd.Content) } case COMMAND_DOCKER_IMAGE_DELETE: { var ( image string force = false ) if im, ok := cmd.Content["image"]; ok { image = im } else { writeOutput(out, true, "--- For Deleting image you need to specifi image name", cmd.Content) return } if f, ok := cmd.Content["force"]; ok { switch f { case "yes", "y", "true", "t": force = true case "no", "n", "false", "f": force = false } } err.Err = DockerClient.RemoveImageExtended(image, docker.RemoveImageOptions{Force: force}) if !err.IsNull() { writeOutput(out, true, fmt.Sprintf("--- Error Deleting image %s : %s", image, err.Error()), cmd.Content) return } writeOutput(out, false, fmt.Sprintf("Image deleted %s", image), cmd.Content) } case COMMAND_DOCKER_CONTAINER_INSPECT: { var ( cont_id string container = make(map[string]interface{}) ) if cid, ok := cmd.Content["container"]; ok { cont_id = cid } else { writeOutput(out, true, "--- Container Name or ID is required during inspecting", cmd.Content) return } container[cont_id], err.Err = DockerClient.InspectContainer(cont_id) writeDcOutput(out, DockerCmdOutput{ Error: false, Message: "Container Inspected Successfully", Data: cmd.Content, DataList: container, }) } case COMMAND_DOCKER_CONTAINER_LIST: { var ( all = false containers []docker.APIContainers w_list = make(map[string]interface{}) ) if a, ok := cmd.Content["all"]; ok { switch a { case "yes", "y", "true", "t": all = true case "no", "n", "false", "f": all = false } } containers, err.Err = DockerClient.ListContainers(docker.ListContainersOptions{All: all}) if !err.IsNull() { writeOutput(out, true, "--- Error Getting Container list", cmd.Content) return } for _, c := range containers { w_list[c.ID], err.Err = DockerClient.InspectContainer(c.ID) if !err.IsNull() { writeDcOutput(out, DockerCmdOutput{ Error: true, Message: fmt.Sprintf("--- Error Inspecting container %s", c.ID), Data: cmd.Content, DataList: w_list, }) } } writeDcOutput(out, DockerCmdOutput{ Error: false, Message: "Containers list fetched successfully", Data: cmd.Content, DataList: w_list, }) } case COMMAND_DOCKER_IMAGE_LIST: { var ( all = false images []docker.APIImages w_list = make(map[string]interface{}) ) if a, ok := cmd.Content["all"]; ok { switch a { case "yes", "y", "true", "t": all = true case "no", "n", "false", "f": all = false } } images, err.Err = DockerClient.ListImages(docker.ListImagesOptions{All: all}) if !err.IsNull() { writeOutput(out, true, "--- Error Getting Image list", cmd.Content) return } for _, im := range images { w_list[im.ID], err.Err = DockerClient.InspectImage(im.ID) if !err.IsNull() { writeDcOutput(out, DockerCmdOutput{ Error: true, Message: fmt.Sprintf("--- Error Inspecting image %s", im.ID), Data: cmd.Content, DataList: w_list, }) } } writeDcOutput(out, DockerCmdOutput{ Error: false, Message: "Images list fetched successfully", Data: cmd.Content, DataList: w_list, }) } } }
func BuildTree(console_conf *TreeScaleConf, silent_build, force, multiple bool) { var ( err tree_lib.TreeError db_dump = tmp_db_dir wg = sync.WaitGroup{} ) var BuildFunc = func(name string, ssh_conf SSHConfig) { var ( input = make(chan string) test_err *ssh.ExitError ) fmt.Println("Connecting to Server -> ", name) err = ssh_conf.Connect() if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(name, " -> ", "Checking Docker availability") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "docker -v"), os.Stdout, os.Stderr, input) if !err.IsNull() { if reflect.TypeOf(err.Err).AssignableTo(reflect.TypeOf(test_err)) { fmt.Println(name, " -> ", "Docker is not Installed, Do you want to install it ? [Y/N]") if silent_build || consoleYesNoWait() { err = installDocker(ssh_conf) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } } else { fmt.Println(name, " -> ", "Docker installation skipped") } } else { fmt.Println(err.Error()) fmt.Println("Terminating...") return } } fmt.Println(name, " -> ", "Checking TreeScale availability") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "treescale v"), os.Stdout, os.Stderr, input) if force || !err.IsNull() { if force || reflect.TypeOf(err.Err).AssignableTo(reflect.TypeOf(test_err)) { fmt.Println(name, " -> ", "TreeScale is not Installed, Do you want to install it ? [Y/N]") if silent_build || consoleYesNoWait() { err = installTreeScale(ssh_conf) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } } else { fmt.Println("Infrastructure Tree will work only with TreeScale software !") fmt.Println("Terminating...") return } } else { fmt.Println(err.Error()) fmt.Println("Terminating...") } } fmt.Println(name, " -> ", "Getting home directory for user ", ssh_conf.Username) home_dir_buf := bytes.NewBuffer([]byte{}) err = ssh_conf.Exec("echo $HOME", home_dir_buf, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } db_copy_path := fmt.Sprintf("%s/tree.db", strings.Replace(home_dir_buf.String(), "\n", "", -1)) fmt.Println(name, " -> ", "Copeing Database dump file ", db_copy_path) err = ssh_conf.CopyFile(db_dump, db_copy_path) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(name, " -> ", "Moving remote file to ", tree_db.DB_DIR) err = ssh_conf.Exec(runSudo(ssh_conf.Password, fmt.Sprintf("mv %s %s", db_copy_path, tree_db.DEFAULT_DB_FILE)), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(name, " -> ", "Setting name for current node") err = ssh_conf.Exec(runSudo(ssh_conf.Password, fmt.Sprintf("treescale node --set-name=%s", name)), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println("Adding private registry SSL exception") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "treescale ssl-exception"), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println("Restarting Docker") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "service docker restart"), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } time.Sleep(time.Second * 1) fmt.Println(name, " -> ", "Checking Docker Registery Server !") for _, reg := range console_conf.Registry { if name == reg.Server { fmt.Println(name, " -> ", "Installing Docker registery") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "docker pull registry:2"), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(fmt.Sprintf("Running Registery container on Port %d", reg.Port)) err = ssh_conf.Exec(runSudo(ssh_conf.Password, fmt.Sprintf("docker run -d -p %d:%d registry:2", reg.Port, reg.Port)), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(fmt.Sprintf("Docker registery runned successfully on IP: %s and Prot: %d", reg.Server, reg.Port)) } } fmt.Println(name, " -> ", "Running TreeScale in daemon mode") err = ssh_conf.Exec(runSudo(ssh_conf.Password, "pkill treescale"), os.Stdout, os.Stderr, input) err = ssh_conf.Exec(runSudo(ssh_conf.Password, "treescale node -d"), os.Stdout, os.Stderr, input) if !err.IsNull() { fmt.Println(err.Error()) fmt.Println("Terminating...") return } fmt.Println(name, " -> ", "Tree component ready") ssh_conf.Disconnect() if multiple { wg.Done() } } err.From = tree_lib.FROM_BUILD_TREE for name, ssh_conf := range console_conf.SSH { if multiple { wg.Add(1) go BuildFunc(name, ssh_conf) } else { BuildFunc(name, ssh_conf) } } if multiple { wg.Wait() } }