scientia-cli.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "flag"
  7. "fmt"
  8. "io"
  9. "log"
  10. "math/rand"
  11. "net/http"
  12. "os"
  13. "gopkg.in/yaml.v2"
  14. )
  15. /**
  16. * scientia
  17. *
  18. * Copyright 2023 - 2024 Johannes Keßler
  19. *
  20. * https://www.bananas-playground.net/projekt/scientia/
  21. *
  22. *
  23. * This program is free software: you can redistribute it and/or modify
  24. * it under the terms of the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE
  25. *
  26. * You should have received a copy of the
  27. * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
  28. * along with this program. If not, see http://www.sun.com/cddl/cddl.html
  29. */
  30. const website = "https://www.bananas-playground.net/projekt/scientia/"
  31. const version = "1.0"
  32. // used for non-existing default config
  33. const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_"
  34. // command line parameters
  35. var optsVerbose bool
  36. var optsCreateConfig bool
  37. var optsDebug bool
  38. // config
  39. var cfg Config
  40. // config file struct
  41. type Config struct {
  42. Endpoint struct {
  43. Host string `yaml:"host"`
  44. Secret string `yaml:"secret"`
  45. } `yaml:"endpoint"`
  46. }
  47. // post json struct
  48. type PayloadJson struct {
  49. Asl string `json:"asl"`
  50. Data string `json:"data"`
  51. }
  52. // response json struct
  53. type Response struct {
  54. Message string `json:"message"`
  55. Status int `json:"status"`
  56. }
  57. /**
  58. * Main
  59. */
  60. func main() {
  61. // https://cli.urfave.org/v2/examples/arguments/
  62. // https://github.com/dnote/dnote
  63. //https://github.com/spf13/cobra/blob/main/site/content/user_guide.md
  64. // parse commandline parameters
  65. flag.BoolVar(&optsVerbose, "verbose", false, "Produce verbose output")
  66. flag.BoolVar(&optsCreateConfig, "create-config-file", false, "Create default config file")
  67. flag.BoolVar(&optsDebug, "debug", false, "Print debug infos")
  68. flag.Parse()
  69. if optsDebug {
  70. fmt.Println("verbose:", optsVerbose)
  71. fmt.Println("create-config-file:", optsCreateConfig)
  72. fmt.Println("debug:", optsDebug)
  73. }
  74. // load the config and populate Config
  75. loadConfig()
  76. // get the payload
  77. payload := getInput()
  78. if optsDebug {
  79. log.Println(payload)
  80. }
  81. // do the upload and get the response
  82. responseString := uploadCall(payload)
  83. response := Response{}
  84. json.Unmarshal([]byte(responseString), &response)
  85. // print the result and link to the pasty
  86. fmt.Printf("Status: %d\n", response.Status)
  87. fmt.Printf("Message: %s\n", response.Message)
  88. }
  89. /**
  90. * Check and display error with additional message
  91. */
  92. func errorCheck(e error, msg string) {
  93. if e != nil {
  94. log.Fatal(msg, " ; Errrormsg: ", e)
  95. }
  96. }
  97. /**
  98. * just a random string
  99. */
  100. func randStringBytes(n int) string {
  101. b := make([]byte, n)
  102. for i := range b {
  103. b[i] = letters[rand.Intn(len(letters))]
  104. }
  105. return string(b)
  106. }
  107. /**
  108. * load or even create a default config
  109. * $HOME/.scientia.yaml
  110. */
  111. func loadConfig() {
  112. homeDir, err := os.UserHomeDir()
  113. errorCheck(err, "No $HOME directory available?")
  114. if optsVerbose {
  115. log.Printf("Your $HOME: %s \n", homeDir)
  116. }
  117. var configFile = homeDir + "/.scientia.yaml"
  118. if _, err := os.Stat(configFile); errors.Is(err, os.ErrNotExist) {
  119. log.Printf("Config file not found: %s \n", configFile)
  120. if optsCreateConfig {
  121. log.Printf("Creating new default config file: %s \n", configFile)
  122. newConfig, err := os.Create(configFile)
  123. errorCheck(err, "Can not create config file!")
  124. defer newConfig.Close()
  125. _, err = fmt.Fprintf(newConfig, "# scientia go client config file.\n")
  126. errorCheck(err, "Can not write to new config file")
  127. fmt.Fprintf(newConfig, "# See %s for more details.\n", website)
  128. fmt.Fprintf(newConfig, "# Version: %s\n", version)
  129. fmt.Fprintf(newConfig, "endpoint:\n")
  130. fmt.Fprintf(newConfig, " host: http://your-scientia-endpoi.nt/api.php\n")
  131. fmt.Fprintf(newConfig, " secret: %s\n", randStringBytes(50))
  132. log.Fatalf("New default config file created: - %s - Edit and launch again!", configFile)
  133. }
  134. }
  135. existingConfigFile, err := os.Open(configFile)
  136. errorCheck(err, "Can not open config file. Did you create one with -create-config-file?")
  137. defer existingConfigFile.Close()
  138. if optsVerbose {
  139. log.Printf("Reading config file: %s \n", configFile)
  140. }
  141. var decoder = yaml.NewDecoder(existingConfigFile)
  142. err = decoder.Decode(&cfg)
  143. errorCheck(err, "Can not decode config file")
  144. if cfg.Endpoint.Host == "" || cfg.Endpoint.Secret == "" {
  145. log.Fatal("Empty config?")
  146. }
  147. if optsDebug {
  148. log.Println(cfg.Endpoint.Host)
  149. log.Println(cfg.Endpoint.Secret)
  150. }
  151. }
  152. /**
  153. * Do a http POST call to the defined endpoint
  154. * and upload the payload
  155. * Return response body as string
  156. */
  157. func uploadCall(payload string) string {
  158. if optsVerbose {
  159. log.Println("Starting to upload data")
  160. }
  161. if optsDebug {
  162. log.Println(payload)
  163. }
  164. if len(payload) == 0 {
  165. log.Fatal("Nothing provided to upload")
  166. }
  167. payloadStruct := PayloadJson{
  168. Asl: cfg.Endpoint.Secret,
  169. Data: payload,
  170. }
  171. jsonData, err := json.Marshal(payloadStruct)
  172. errorCheck(err, "Can not create json payload")
  173. req, err := http.NewRequest(http.MethodPost, cfg.Endpoint.Host, bytes.NewBuffer(jsonData))
  174. errorCheck(err, "Can not create http request")
  175. // We need to set the content type from the writer, it includes necessary boundary as well
  176. req.Header.Set("Content-Type", "application/json; charset=UTF-8")
  177. req.Header.Set("User-Agent", "scientiaAgent/1.0")
  178. // Do the request
  179. client := &http.Client{}
  180. response, err := client.Do(req)
  181. errorCheck(err, "POST request failed")
  182. responseBody, err := io.ReadAll(response.Body)
  183. errorCheck(err, "Can not read response body")
  184. if optsVerbose {
  185. log.Println("Request done")
  186. }
  187. if optsDebug {
  188. log.Printf("Response status code: %d\n", response.StatusCode)
  189. log.Printf("Response headers: %#v\n", response.Header)
  190. log.Println(string(responseBody))
  191. }
  192. return string(responseBody)
  193. }
  194. /**
  195. * check if file is provided as commandline argument
  196. * or piped into
  197. * return the read data as string
  198. */
  199. func getInput() string {
  200. if optsVerbose {
  201. log.Println("Getting input")
  202. }
  203. var inputString string
  204. if filename := flag.Arg(0); filename != "" {
  205. if optsVerbose {
  206. log.Println("Read from file argument")
  207. }
  208. bytes, err := os.ReadFile(filename)
  209. errorCheck(err, "Error opening file")
  210. inputString = string(bytes)
  211. } else {
  212. stat, _ := os.Stdin.Stat()
  213. if (stat.Mode() & os.ModeCharDevice) == 0 {
  214. if optsVerbose {
  215. log.Println("data is being piped")
  216. }
  217. bytes, _ := io.ReadAll(os.Stdin)
  218. inputString = string(bytes)
  219. }
  220. }
  221. if len(inputString) == 0 {
  222. log.Fatal("Nothing provided to upload")
  223. }
  224. return inputString
  225. }