relays.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // Copyright Joyent, Inc.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "fmt"
  10. "net/url"
  11. "regexp"
  12. "sort"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/gofrs/uuid"
  17. "github.com/jawher/mow.cli"
  18. "github.com/olekukonko/tablewriter"
  19. )
  20. type Relays struct {
  21. *Conch
  22. }
  23. func (c *Conch) Relays() *Relays {
  24. return &Relays{c}
  25. }
  26. type RelayList []Relay
  27. func (r RelayList) Len() int {
  28. return len(r)
  29. }
  30. func (r RelayList) Swap(i, j int) {
  31. r[i], r[j] = r[j], r[i]
  32. }
  33. func (r RelayList) Less(i, j int) bool {
  34. return r[i].Updated.Before(r[j].Updated)
  35. }
  36. func (rl RelayList) String() string {
  37. sort.Sort(rl)
  38. if API.JsonOnly {
  39. return API.AsJSON(rl)
  40. }
  41. tableString := &strings.Builder{}
  42. table := tablewriter.NewWriter(tableString)
  43. TableToMarkdown(table)
  44. table.SetHeader([]string{
  45. "Serial",
  46. "Name",
  47. "Version",
  48. "IP",
  49. "SSH Port",
  50. "Updated",
  51. })
  52. for _, r := range rl {
  53. table.Append([]string{
  54. r.SerialNumber,
  55. r.Name,
  56. r.Version,
  57. r.IpAddr,
  58. strconv.Itoa(r.SshPort),
  59. TimeStr(r.Updated),
  60. })
  61. }
  62. table.Render()
  63. return tableString.String()
  64. }
  65. type Relay struct {
  66. ID uuid.UUID `json:"id"`
  67. SerialNumber string `json:"serial_number"`
  68. Name string `json:"name,omitempty"`
  69. Version string `json:"version,omitempty"`
  70. IpAddr string `json:"ipaddr,omitempty"`
  71. SshPort int `json:"ssh_port"`
  72. Created time.Time `json:"created"`
  73. Updated time.Time `json:"updated"`
  74. }
  75. func (r Relay) String() string {
  76. if API.JsonOnly {
  77. return API.AsJSON(r)
  78. }
  79. t, err := NewTemplate().Parse(relayTemplate)
  80. if err != nil {
  81. panic(err)
  82. }
  83. buf := new(bytes.Buffer)
  84. if err := t.Execute(buf, r); err != nil {
  85. panic(err)
  86. }
  87. return buf.String()
  88. }
  89. func (r *Relays) GetAll() RelayList {
  90. rl := make(RelayList, 0)
  91. res := r.Do(r.Sling().New().Get("/relay/?no_devices=1"))
  92. if ok := res.Parse(&rl); !ok {
  93. panic(res)
  94. }
  95. return rl
  96. }
  97. func (r *Relays) Get(identifier string) (relay Relay) {
  98. uri := fmt.Sprintf(
  99. "/relay/%s",
  100. identifier,
  101. )
  102. res := r.Do(r.Sling().New().Get(uri))
  103. if ok := res.Parse(&relay); !ok {
  104. panic(res)
  105. }
  106. return relay
  107. }
  108. func (r *Relays) Register(
  109. serial string,
  110. version string,
  111. ipaddr string,
  112. name string,
  113. sshPort int,
  114. ) Relay {
  115. if serial == "" {
  116. panic("please provide a serial number")
  117. }
  118. out := struct {
  119. Serial string `json:"serial"`
  120. Version string `json:"version,omitempty"`
  121. IpAddr string `json:"ipaddr,omitempty"`
  122. Name string `json:"alias,omitempty"`
  123. SshPort int `json:"ssh_port,omitempty"`
  124. }{
  125. serial,
  126. version,
  127. ipaddr,
  128. name,
  129. sshPort,
  130. }
  131. uri := fmt.Sprintf(
  132. "/relay/%s/register",
  133. url.PathEscape(serial),
  134. )
  135. res := r.Do(
  136. r.Sling().Post(uri).
  137. Set("Content-Type", "application/json").
  138. BodyJSON(out),
  139. )
  140. var relay Relay
  141. if ok := res.Parse(&relay); !ok {
  142. panic(res)
  143. }
  144. return relay
  145. }
  146. func (r *Relays) Delete(identifier string) {
  147. uri := fmt.Sprintf("/relay/%s", url.PathEscape(identifier))
  148. res := r.Do(r.Sling().New().Delete(uri))
  149. if res.StatusCode() != 204 {
  150. // I know this is weird. Like in other places, it should be impossible
  151. // to reach here unless the status code is 204. The API returns 204
  152. // (which gets us here) or 409 (which will explode before it gets here).
  153. // If we got here via some other code, then there's some new behavior
  154. // that we need to know about.
  155. panic(res)
  156. }
  157. }
  158. func init() {
  159. App.Command("relays", "Perform actions against the whole list of relays", func(cmd *cli.Cmd) {
  160. cmd.Command("get", "Get a list of relays", func(cmd *cli.Cmd) {
  161. cmd.Action = func() { fmt.Println(API.Relays().GetAll()) }
  162. })
  163. cmd.Command("find", "Find relays by name", func(cmd *cli.Cmd) {
  164. var (
  165. relays = cmd.StringsArg("RELAYS", nil, "List of regular expressions to match against relay IDs")
  166. andOpt = cmd.BoolOpt("and", false, "Match the list as a logical AND")
  167. )
  168. cmd.Spec = "[OPTIONS] RELAYS..."
  169. cmd.LongDesc = `
  170. Takes a list of regular expressions and matches those against the IDs of all known relays.
  171. The default behavior is to match as a logical OR but this behavior can be changed by providing the --and flag
  172. For instance:
  173. * "conch relays find drd" will find all relays with 'drd' in their ID. For perl folks, this is essentially 'm/drd/'
  174. * "conch relays find '^ams-'" will find all relays with IDs that begin with 'ams-'
  175. * "conch relays find drd '^ams-' will find all relays with IDs that contain 'drd' OR begin with 'ams-'
  176. * "conch relays find --and drd '^ams-' will find all relays with IDs that contain 'drd' AND begin with '^ams-'`
  177. cmd.Action = func() {
  178. if *relays == nil {
  179. panic("please provide a list of regular expressions")
  180. }
  181. // If a user for some strange reason gives us a relay name of "", the
  182. // cli lib will pass it on to us. That name is obviously useless so
  183. // let's filter it out.
  184. relayREs := make([]*regexp.Regexp, 0)
  185. for _, matcher := range *relays {
  186. if matcher == "" {
  187. continue
  188. }
  189. re, err := regexp.Compile(matcher)
  190. if err != nil {
  191. panic(err)
  192. }
  193. relayREs = append(relayREs, re)
  194. }
  195. if len(relayREs) == 0 {
  196. panic("please provide a list of regular expressions")
  197. }
  198. results := make(RelayList, 0)
  199. for _, relay := range API.Relays().GetAll() {
  200. matched := 0
  201. for _, re := range relayREs {
  202. if re.MatchString(relay.SerialNumber) {
  203. if *andOpt {
  204. matched++
  205. } else {
  206. results = append(results, relay)
  207. continue
  208. }
  209. }
  210. }
  211. if *andOpt {
  212. if matched == len(relayREs) {
  213. results = append(results, relay)
  214. }
  215. }
  216. }
  217. fmt.Println(results)
  218. }
  219. })
  220. })
  221. App.Command("relay", "Perform actions against a single relay", func(cmd *cli.Cmd) {
  222. relayArg := cmd.StringArg(
  223. "RELAY",
  224. "",
  225. "ID of the relay",
  226. )
  227. cmd.Spec = "RELAY"
  228. cmd.Command("get", "Get data about a single relay", func(cmd *cli.Cmd) {
  229. cmd.Action = func() { fmt.Println(API.Relays().Get(*relayArg)) }
  230. })
  231. cmd.Command("register", "Register a relay with the API", func(cmd *cli.Cmd) {
  232. var (
  233. versionOpt = cmd.StringOpt("version", "", "The version of the relay")
  234. sshPortOpt = cmd.IntOpt("ssh_port port", 22, "The SSH port for the relay")
  235. ipAddrOpt = cmd.StringOpt("ipaddr ip", "", "The IP address for the relay")
  236. nameOpt = cmd.StringOpt("name", "", "The name of the relay")
  237. )
  238. cmd.Action = func() {
  239. fmt.Println(API.Relays().Register(
  240. *relayArg,
  241. *versionOpt,
  242. *ipAddrOpt,
  243. *nameOpt,
  244. *sshPortOpt,
  245. ))
  246. }
  247. })
  248. cmd.Command("delete rm", "Delete a relay", func(cmd *cli.Cmd) {
  249. cmd.Action = func() {
  250. API.Relays().Delete(*relayArg)
  251. fmt.Println(API.Relays().GetAll())
  252. }
  253. })
  254. })
  255. }