func repositoryList(apps []string) { w := tabwriter.NewWriter(os.Stdout, 30, 1, 3, ' ', 0) titles := []string{"NAME", "TAG", "TYPE", "PRIVACY", "SOURCE"} fmt.Fprintln(w, strings.Join(titles, "\t")) repositories := []string{} var repository map[string]interface{} for _, app := range apps { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/repositories/%s", app), nil) internal.Check(json.Unmarshal(b, &repositories)) for _, repositoryID := range repositories { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/repositories/%s/%s", app, repositoryID), nil) internal.Check(json.Unmarshal(b, &repository)) tags := repository["tags"] if tags == "" { tags = "-" } source := repository["source"] if source == nil || source == "" { source = "-" } fmt.Fprintf(w, "%s/%s\t%s\t%s\t%s\t%s\n", app, repository["name"], tags, repository["type"], repository["privacy"], source) w.Flush() } } }
func serviceDomainDetach(serviceID, domain string, args domainStruct) { // Split namespace and service host, app, service, tag, err := internal.ParseResourceName(serviceID) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } else if len(tag) > 0 { fmt.Fprintf(os.Stderr, "Error: Invalid service name. Please see sail service domain detach --help\n") os.Exit(1) } body, err := json.Marshal(args) internal.Check(err) // Sanity checks err = internal.CheckName(domain) internal.Check(err) path := fmt.Sprintf("/applications/%s/services/%s/attached-routes/%s", app, service, domain) data := internal.DeleteBodyWantJSON(path, body) internal.FormatOutput(data, func(data []byte) { fmt.Fprintf(os.Stderr, "Detached route %s %s%s from service %s/%s\n", args.Method, domain, args.Pattern, app, service) }) }
// serviceScale start service (without attach) func serviceScale(app string, service string, number int, destroy bool, batch bool) { if !batch { internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) } path := fmt.Sprintf("/applications/%s/services/%s/scale?stream", app, service) args := Scale{ Number: number, Destroy: destroy, } data, err := json.Marshal(&args) internal.Check(err) buffer, _, err := internal.Stream("POST", path, data) internal.Check(err) line, err := internal.DisplayStream(buffer) internal.Check(err) if line != nil { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) fmt.Printf("Running containers: %v/%v\n", data["container_number"], data["container_target"]) } if !batch { internal.ExitAfterCtrlC() } }
func doServiceRedeploy(args Redeploy, app, service string) { path := fmt.Sprintf("/applications/%s/services/%s/redeploy", app, service) body, err := json.MarshalIndent(args, " ", " ") if err != nil { fmt.Fprintf(os.Stderr, "Fatal: %s\n", err) return } // Attach console if !redeployBatch { internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) } // Redeploy buffer, _, err := internal.Stream("POST", path, body) if err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) } line, err := internal.DisplayStream(buffer) internal.Check(err) if len(line) > 0 { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) } if !redeployBatch { internal.ExitAfterCtrlC() } }
func networkShow(networkID string) { // Split namespace and repository host, app, net, tag, err := internal.ParseResourceName(networkID) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } else if len(tag) > 0 { fmt.Fprintf(os.Stderr, "Error: Invalid network name. Please see sail network show --help\n") os.Exit(1) } var network map[string]interface{} var ranges []string b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks/%s", app, net), nil) internal.Check(json.Unmarshal(b, &network)) brange := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks/%s/ranges", app, net), nil) internal.Check(json.Unmarshal(brange, &ranges)) network["range"] = ranges n, err := json.Marshal(network) internal.Check(err) internal.FormatOutputDef(n) }
func cmdUp(cmd *cobra.Command, args []string) { // Check args if len(args) != 1 { internal.Exit("Invalid usage. sail compose up <namespace>. Please see sail compose up -h\n") } ns := args[0] // Try to read file payload, err := ioutil.ReadFile(upFile) if err != nil { internal.Exit("Error reading compose file: %s\n", err) } // Execute request path := fmt.Sprintf("/applications/%s/fig/up?stream", ns) buffer, _, err := internal.Stream("POST", path, payload, internal.SetHeader("Content-Type", "application/x-yaml")) internal.Check(err) // Display api stream line, err := internal.DisplayStream(buffer) internal.Check(err) if line != nil { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) fmt.Printf("Running containers: %v/%v\n", data["container_number"], data["container_target"]) } }
// serviceStart start service (without attach) func serviceStart(app string, service string, batch bool) { if !batch { internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) } // stream service events in a goroutine internal.EventStreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/events", app, service), nil, true) path := fmt.Sprintf("/applications/%s/services/%s/start", app, service) buffer, _, err := internal.Stream("POST", path, []byte("{}")) internal.Check(err) line, err := internal.DisplayStream(buffer) internal.Check(err) if len(line) > 0 { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) } if !batch { internal.ExitAfterCtrlC() } }
// serviceScale start service (without attach) func serviceScale(app string, service string, number int, destroy bool, batch bool) { if !batch { internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) } // stream service events in a goroutine internal.EventStreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/events", app, service), nil, true) path := fmt.Sprintf("/applications/%s/services/%s/scale", app, service) args := Scale{ Number: number, Destroy: destroy, } data, err := json.Marshal(&args) internal.Check(err) buffer, _, err := internal.Stream("POST", path, data) internal.Check(err) line, err := internal.DisplayStream(buffer) internal.Check(err) if len(line) > 0 { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) } if !batch { internal.ExitAfterCtrlC() } }
func serviceList(apps []string) { w := tabwriter.NewWriter(os.Stdout, 27, 1, 2, ' ', 0) titles := []string{"NAME", "REPOSITORY", "IMAGE ID", "STATE", "CONTAINERS", "CREATED", "NETWORK"} fmt.Fprintln(w, strings.Join(titles, "\t")) services := []string{} var service map[string]interface{} for _, app := range apps { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/services", app), nil) internal.Check(json.Unmarshal(b, &services)) for _, serviceID := range services { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/services/%s", app, serviceID), nil) internal.Check(json.Unmarshal(b, &service)) ips := []string{} for _, container := range service["containers"].(map[string]interface{}) { for name, network := range container.(map[string]interface{})["network"].(map[string]interface{}) { ips = append(ips, fmt.Sprintf("%s:%s", name, network.(map[string]interface{})["ip"])) } } fmt.Fprintf(w, "%s/%s\t%s@%s\t%s\t%s\t%d\t%s\t%s\n", app, service["name"], service["repository"], service["repository_tag"], service["image"].(string)[:12], strings.ToUpper(service["state"].(string)), int(service["container_number"].(float64)), service["creation_date"].(string)[:19], strings.Join(ips, ",")) w.Flush() } } }
func domainListFormatter(data []byte) { var routes []map[string]interface{} internal.Check(json.Unmarshal(data, &routes)) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) // below this: horrible hack. Do I feel ashamed: Yes. if !domainHeadersDone { titles := []string{"APP", "SERVICE", "DOMAIN", "METHOD", "PATTERN"} fmt.Fprintln(w, strings.Join(titles, "\t")) domainHeadersDone = true } for _, route := range routes { app := route["namespace"] service := route["service"] if app == nil { app = "-" } if service == nil { service = "-" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", app, service, route["domain"], route["method"], route["pattern"]) w.Flush() } }
func sshKeyList() { type keyStruct struct { Name string `json:"name"` Fingerprint string `json:"fingerprint"` PublicKey string `json:"public_key"` } b := internal.ReqWant("GET", http.StatusOK, "/user/keys", nil) var keys []keyStruct err := json.Unmarshal(b, &keys) internal.Check(err) w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0) titles := []string{"NAME", "FINGERPRINT"} fmt.Fprintln(w, strings.Join(titles, "\t")) for _, key := range keys { fmt.Fprintf(w, "%s\t%s\n", key.Name, key.Fingerprint, ) } w.Flush() }
func cmdLogs(cmd *cobra.Command, args []string) { usage := "usage: sail containers logs [<applicationName>/]<containerId>" if len(args) != 1 { fmt.Fprintln(os.Stderr, usage) os.Exit(1) } // Split namespace and container host, app, container, tag, err := internal.ParseResourceName(args[0]) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } else if len(tag) > 0 { fmt.Fprintf(os.Stderr, "Error: Invalid container name. Please see sail container logs --help\n") os.Exit(1) } // Get args logsBody.Application = app logsBody.Container = container containerLogs(logsBody) }
func cmdAdd(cmd *cobra.Command, args []string) { cmdAddBody.ContainerNetwork = make(map[string]map[string][]string) cmdAddBody.Links = make(map[string]string) cmdAddBody.ContainerPorts = make(map[string][]PortConfig) cmdAddBody.ContainerCommand = make([]string, 0) if len(args) < 2 { fmt.Fprintln(os.Stderr, cmdAddUsage) os.Exit(1) } // Split namespace and repository host, app, repo, tag, err := internal.ParseResourceName(args[0]) internal.Check(err) cmdAddBody.Application = app cmdAddBody.Repository = repo cmdAddBody.RepositoryTag = tag if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } // Service name if len(args) >= 2 { cmdAddBody.Service = args[1] } else { cmdAddBody.Service = cmdAddBody.Repository } serviceAdd(cmdAddBody) }
func containerList(apps []string) { w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) titles := []string{"APPLICATION", "SERVICE", "CONTAINER", "STATE", "DEPLOYED"} fmt.Fprintln(w, strings.Join(titles, "\t")) containers := []string{} var container map[string]interface{} for _, app := range apps { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/containers", app), nil) internal.Check(json.Unmarshal(b, &containers)) for _, containerID := range containers { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/containers/%s", app, containerID), nil) internal.Check(json.Unmarshal(b, &container)) fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t\n", app, container["service"], container["name"], strings.ToUpper(container["state"].(string)), container["deployment_date"]) w.Flush() } } }
func repositoryAdd(repositoryName string, args repositoryAddStruct) { // Split namespace and repository host, app, repo, tag, err := internal.ParseResourceName(repositoryName) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } else if len(tag) > 0 { fmt.Fprintf(os.Stderr, "Error: Invalid repository name. Please see sail repository add --help\n") os.Exit(1) } body, err := json.Marshal(args) internal.Check(err) path := fmt.Sprintf("/repositories/%s/%s", app, repo) internal.FormatOutputDef(internal.PostBodyWantJSON(path, body)) }
func networkAdd(networkID string, args networkAddStruct) { // Split namespace and repository host, app, net, tag, err := internal.ParseResourceName(networkID) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } else if len(tag) > 0 { fmt.Fprintf(os.Stderr, "Error: Invalid network name. Please see sail network add --help\n") os.Exit(1) } body, err := json.Marshal(args) internal.Check(err) path := fmt.Sprintf("/applications/%s/networks/%s", app, net) internal.FormatOutputDef(internal.PostBodyWantJSON(path, body)) }
func cmdUp(cmd *cobra.Command, args []string) { // FIXME: duplicate internal.ReadConfig() var ns string // Check args if len(args) > 1 { internal.Exit("Invalid usage. sail compose up [<application>]. Please see sail compose up -h\n") } else if len(args) > 1 { ns = args[0] } else { ns = internal.User } // Try to read file payload, err := ioutil.ReadFile(upFile) if err != nil { internal.Exit("Error reading compose file: %s\n", err) } // Execute request path := fmt.Sprintf("/applications/%s/fig/up", ns) buffer, _, err := internal.Stream("POST", path, payload, internal.SetHeader("Content-Type", "application/x-yaml")) internal.Check(err) // Display api stream line, err := internal.DisplayStream(buffer) internal.Check(err) if len(line) > 0 { var data []map[string]interface{} err = json.Unmarshal(line, &data) if err != nil { fmt.Printf("Error detected in API Return. Line: %s\n", line) return } for i := range data { fmt.Printf("Compose operation for service %v is %v\n", data[i]["name"], data[i]["result"]) } } }
func serviceDelete(namespace string, name string) { path := fmt.Sprintf("/applications/%s/services/%s?force=%t", namespace, name, deleteForce) buffer, _, err := internal.Stream("DELETE", path, nil) if err != nil { fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) } _, err = internal.DisplayStream(buffer) internal.Check(err) }
func networkShow(networkID string) { t := strings.Split(networkID, "/") if len(t) != 2 { fmt.Fprintln(os.Stderr, "Invalid usage. sail network show <applicationName>/<networkId>. Please see sail network show --help") } else { var network map[string]interface{} var ranges []string b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks/%s", t[0], t[1]), nil) internal.Check(json.Unmarshal(b, &network)) brange := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks/%s/ranges", t[0], t[1]), nil) internal.Check(json.Unmarshal(brange, &ranges)) network["range"] = ranges n, err := json.Marshal(network) internal.Check(err) internal.FormatOutputDef(n) } }
func parsePublishedPort(args []string) map[string][]PortConfig { v := make(map[string][]PortConfig) for _, pub := range args { split := strings.Split(pub, ":") if len(split) == 1 { // containerPort port, err := parsePort(split[0]) internal.Check(err) v[split[0]+"/tcp"] = []PortConfig{PortConfig{PublishedPort: port}} } else if len(split) == 2 { // network:containerPort, publishedPort:containerPort port, err := strconv.Atoi(split[0]) if err != nil { // network:containerPort key := split[1] + "/tcp" port, err = parsePort(split[1]) internal.Check(err) v[key] = append(v[key], PortConfig{PublishedPort: port, Network: split[0]}) } else { // publishedPort:containerPort key := split[1] + "/tcp" port, err = parsePort(split[0]) internal.Check(err) v[key] = append(v[key], PortConfig{PublishedPort: port}) } } else if len(split) == 3 { // network:publishedPort:containerPort, network::containerPort if split[1] == "" { split[1] = split[2] } port, err := parsePort(split[1]) internal.Check(err) key := split[2] + "/tcp" v[key] = append(v[key], PortConfig{PublishedPort: port, Network: split[0]}) } else { fmt.Fprintf(os.Stderr, "Error: Invalid port expose rule '%s'\n", pub) os.Exit(1) } } return v }
func serviceAttach(serviceID string) { // Split namespace and service host, app, service, _, err := internal.ParseResourceName(serviceID) internal.Check(err) if !internal.CheckHostConsistent(host) { fmt.Fprintf(os.Stderr, "Error: Invalid Host %s for endpoint %s\n", host, internal.Host) os.Exit(1) } internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) internal.ExitAfterCtrlC() }
func webhookDelete(namespace, webhookURL string) { urlEscape := url.QueryEscape(webhookURL) path := fmt.Sprintf("/applications/%s/hook", namespace) // pass urlEscape as query string argument BaseURL, err := url.Parse(path) internal.Check(err) params := url.Values{} params.Add("url", urlEscape) BaseURL.RawQuery = params.Encode() internal.FormatOutputDef(internal.DeleteWantJSON(BaseURL.String())) }
func networkAdd(networkID string, args networkAddStruct) { t := strings.Split(networkID, "/") if len(t) != 2 { fmt.Fprintln(os.Stderr, "Invalid usage. sail network add <applicationName>/<networkId>. Please see sail network add --help") return } body, err := json.Marshal(args) internal.Check(err) path := fmt.Sprintf("/applications/%s/networks/%s", t[0], t[1]) internal.FormatOutputDef(internal.PostBodyWantJSON(path, body)) }
func serviceDomainDetach(serviceID, domain string, args domainStruct) { t := strings.Split(serviceID, "/") if len(t) != 2 { fmt.Fprintln(os.Stderr, usageDomainDetach) return } body, err := json.Marshal(args) internal.Check(err) path := fmt.Sprintf("/applications/%s/services/%s/attached-routes/%s", t[0], t[1], domain) internal.FormatOutputDef(internal.DeleteBodyWantJSON(path, body)) }
func serviceLogsFormatter(data []byte) { logs := [][]string{} err := json.Unmarshal(data, &logs) internal.Check(err) w := tabwriter.NewWriter(os.Stdout, 20, 1, 3, ' ', 0) titles := []string{"TIMESTAMP", "ID", "LOG"} fmt.Fprintln(w, strings.Join(titles, "\t")) for i := range logs { fmt.Fprintf(w, "%s\t%s\t%s\n", logs[i][0], logs[i][1], logs[i][2]) w.Flush() } }
func sshKeyDelete(fingerprint string) { urlEscape := url.QueryEscape(fingerprint) path := "/user/keys" // pass urlEscape as query string argument BaseURL, err := url.Parse(path) internal.Check(err) params := url.Values{} params.Add("fingerprint", urlEscape) BaseURL.RawQuery = params.Encode() internal.FormatOutputDef(internal.DeleteWantJSON(BaseURL.String())) }
// serviceStart start service (without attach) func serviceStart(app string, service string, batch bool) { if !batch { internal.StreamPrint("GET", fmt.Sprintf("/applications/%s/services/%s/attach", app, service), nil) } path := fmt.Sprintf("/applications/%s/services/%s/start?stream", app, service) buffer, _, err := internal.Stream("POST", path, []byte("{}")) internal.Check(err) line, err := internal.DisplayStream(buffer) internal.Check(err) if line != nil { var data map[string]interface{} err = json.Unmarshal(line, &data) internal.Check(err) fmt.Printf("Hostname: %v\n", data["hostname"]) fmt.Printf("Running containers: %v/%v\n", data["container_number"], data["container_target"]) } if !batch { internal.ExitAfterCtrlC() } }
func serviceDomainAttach(serviceID, domain, pattern, method string) { t := strings.Split(serviceID, "/") if len(t) != 2 { fmt.Fprintln(os.Stderr, usageDomainAttach) return } args := domainStruct{Pattern: pattern, Method: method} body, err := json.Marshal(args) internal.Check(err) path := fmt.Sprintf("/applications/%s/services/%s/attached-routes/%s", t[0], t[1], domain) internal.FormatOutputDef(internal.PostBodyWantJSON(path, body)) }
func networkList(apps []string) { w := tabwriter.NewWriter(os.Stdout, 30, 1, 3, ' ', 0) titles := []string{"NAME", "SUBNET"} fmt.Fprintln(w, strings.Join(titles, "\t")) networks := []string{} var network map[string]interface{} for _, app := range apps { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks", app), nil) internal.Check(json.Unmarshal(b, &networks)) for _, networkID := range networks { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/networks/%s", app, networkID), nil) internal.Check(json.Unmarshal(b, &network)) subnet := network["subnet"] if network["subnet"] == nil || network["subnet"] == "" { subnet = "-" } fmt.Fprintf(w, "%s\t%s\n", network["name"], subnet) w.Flush() } } }
func domainListNamespace(namespace, service string) { var services []string // TODO: rewrite whithout the m(n+1)+1... (needs API) if len(service) > 0 { services = append(services, service) } else { b := internal.ReqWant("GET", http.StatusOK, fmt.Sprintf("/applications/%s/services", namespace), nil) internal.Check(json.Unmarshal(b, &services)) } for _, service := range services { domainListService(namespace, service) } }