diff --git a/cmd/tunasynctl/tunasynctl.go b/cmd/tunasynctl/tunasynctl.go new file mode 100644 index 0000000..b6fe8b1 --- /dev/null +++ b/cmd/tunasynctl/tunasynctl.go @@ -0,0 +1,252 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + + "github.com/codegangsta/cli" + "gopkg.in/op/go-logging.v1" + + tunasync "github.com/tuna/tunasync/internal" +) + +const ( + listJobsPath = "/jobs" + listWorkersPath = "/workers" + cmdPath = "/cmd" +) + +var logger = logging.MustGetLogger("tunasynctl-cmd") + +var baseURL string +var client *http.Client + +func initializeWrapper(handler func(*cli.Context)) func(*cli.Context) { + return func(c *cli.Context) { + err := initialize(c) + if err != nil { + os.Exit(1) + } + handler(c) + } + +} + +func initialize(c *cli.Context) error { + // init logger + tunasync.InitLogger(c.Bool("verbose"), c.Bool("verbose"), false) + + // parse manager server address + baseURL = c.String("manager") + if baseURL == "" { + baseURL = "localhost" + } + managerPort := c.String("port") + if managerPort != "" { + baseURL += ":" + managerPort + } + if c.Bool("no-ssl") { + baseURL = "http://" + baseURL + } else { + baseURL = "https://" + baseURL + } + logger.Info("Use manager address: %s", baseURL) + + // create HTTP client + var err error + client, err = tunasync.CreateHTTPClient(c.String("ca-cert")) + if err != nil { + err = fmt.Errorf("Error initializing HTTP client: %s", err.Error()) + logger.Error(err.Error()) + return err + + } + return nil +} + +func listWorkers(c *cli.Context) { + var workers []tunasync.WorkerStatus + _, err := tunasync.GetJSON(baseURL+listWorkersPath, &workers, client) + if err != nil { + logger.Error("Filed to correctly get informations from manager server: %s", err.Error()) + os.Exit(1) + } + + b, err := json.MarshalIndent(workers, "", " ") + if err != nil { + logger.Error("Error printing out informations: %s", err.Error()) + } + fmt.Print(string(b)) +} + +func listJobs(c *cli.Context) { + // FIXME: there should be an API on manager server side that return MirrorStatus list to tunasynctl + var jobs []tunasync.MirrorStatus + if c.Bool("all") { + _, err := tunasync.GetJSON(baseURL+listJobsPath, &jobs, client) + if err != nil { + logger.Error("Filed to correctly get information of all jobs from manager server: %s", err.Error()) + os.Exit(1) + } + + } else { + args := c.Args() + if len(args) == 0 { + logger.Error("Usage Error: jobs command need at least one arguments or \"--all\" flag.") + os.Exit(1) + } + ans := make(chan []tunasync.MirrorStatus, len(args)) + for _, workerID := range args { + go func(workerID string) { + var workerJobs []tunasync.MirrorStatus + _, err := tunasync.GetJSON(fmt.Sprintf("%s/workers/%s/jobs", baseURL, workerID), &workerJobs, client) + if err != nil { + logger.Error("Filed to correctly get jobs for worker %s: %s", workerID, err.Error()) + } + ans <- workerJobs + }(workerID) + } + for range args { + jobs = append(jobs, <-ans...) + } + } + + b, err := json.MarshalIndent(jobs, "", " ") + if err != nil { + logger.Error("Error printing out informations: %s", err.Error()) + } + fmt.Printf(string(b)) +} + +func cmdJob(cmd tunasync.CmdVerb) func(*cli.Context) { + return func(c *cli.Context) { + var mirrorID string + var argsList []string + if len(c.Args()) == 1 { + mirrorID = c.Args()[0] + } else if len(c.Args()) == 2 { + mirrorID = c.Args()[0] + for _, arg := range strings.Split(c.Args()[1], ",") { + argsList = append(argsList, strings.TrimSpace(arg)) + } + } else { + logger.Error("Usage Error: cmd command receive just 1 required positional argument MIRROR and 1 optional ") + os.Exit(1) + } + + cmd := tunasync.ClientCmd{ + Cmd: cmd, + MirrorID: mirrorID, + WorkerID: c.String("worker"), + Args: argsList, + } + resp, err := tunasync.PostJSON(baseURL+cmdPath, cmd, client) + if err != nil { + logger.Error("Failed to correctly send command: %s", err.Error) + os.Exit(1) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + logger.Error("Failed to parse response: %s", err.Error()) + } + + logger.Error("Failed to correctly send command: HTTP status code is not 200: %s", body) + } else { + logger.Info("Succesfully send command") + } + } +} + +func main() { + app := cli.NewApp() + app.EnableBashCompletion = true + app.Version = "0.1" + + commonFlags := []cli.Flag{ + cli.StringFlag{ + Name: "manager, m", + Usage: "The manager server address", + }, + cli.StringFlag{ + Name: "port, p", + Usage: "The manager server port", + }, + cli.StringFlag{ + Name: "ca-cert, c", + Usage: "Trust CA cert `CERT`", + }, + + cli.BoolFlag{ + Name: "no-ssl", + Usage: "Use http rather than https", + }, + cli.BoolFlag{ + Name: "verbose, v", + Usage: "Enable verbosely logging", + }, + } + cmdFlags := []cli.Flag{ + cli.StringFlag{ + Name: "worker, w", + Usage: "Send the command to `WORKER`", + }, + } + + app.Commands = []cli.Command{ + { + Name: "list", + Usage: "List jobs of workers", + Flags: append(commonFlags, + []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "List all jobs of all workers", + }, + }...), + Action: initializeWrapper(listJobs), + }, + { + Name: "workers", + Usage: "List workers", + Flags: commonFlags, + Action: initializeWrapper(listWorkers), + }, + { + Name: "start", + Usage: "Start a job", + Flags: append(commonFlags, cmdFlags...), + Action: initializeWrapper(cmdJob(tunasync.CmdStart)), + }, + { + Name: "stop", + Usage: "Stop a job", + Flags: append(commonFlags, cmdFlags...), + Action: initializeWrapper(cmdJob(tunasync.CmdStop)), + }, + { + Name: "disable", + Usage: "Disable a job", + Flags: append(commonFlags, cmdFlags...), + Action: initializeWrapper(cmdJob(tunasync.CmdDisable)), + }, + { + Name: "restart", + Usage: "Restart a job", + Flags: append(commonFlags, cmdFlags...), + Action: initializeWrapper(cmdJob(tunasync.CmdRestart)), + }, + { + Name: "ping", + Flags: append(commonFlags, cmdFlags...), + Action: initializeWrapper(cmdJob(tunasync.CmdPing)), + }, + } + app.Run(os.Args) +}