client.go 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394
  1. package semaphore
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "git.aetherial.dev/aeth/yosai/pkg/daemon"
  13. "git.aetherial.dev/aeth/yosai/pkg/keytags"
  14. "gopkg.in/yaml.v3"
  15. )
  16. const ProjectsPath = "api/projects"
  17. const ProjectPath = "api/project"
  18. const YosaiProject = "Yosai VPN Sentinel"
  19. const YosaiServerInventory = "Yosai VPN Servers"
  20. const YosaiVpnRotationJob = "VPN Rotation playbook"
  21. const YosaiEnvironment = "VPN Server configuration environment variables"
  22. type SemaphoreConnection struct {
  23. Client *http.Client
  24. Keyring daemon.DaemonKeyRing
  25. KeyTagger keytags.Keytagger
  26. Config *daemon.ConfigFromFile
  27. ServerUrl string
  28. HttpProto string
  29. ProjectId int
  30. }
  31. type TaskInfo struct {
  32. ID int `json:"id"`
  33. TemplateID int `json:"template_id"`
  34. Status string `json:"status"`
  35. Debug bool `json:"debug"`
  36. Playbook string `json:"playbook"`
  37. Environment string `json:"environment"`
  38. Secret string `json:"secret"`
  39. Limit string `json:"limit"`
  40. }
  41. type TaskOutput struct {
  42. TaskID int `json:"task_id"`
  43. Task string `json:"task"`
  44. Time time.Time `json:"time"`
  45. Output string `json:"output"`
  46. }
  47. type NewTemplateRequest struct {
  48. ProjectId int `json:"project_id"`
  49. Name string `json:"name"`
  50. InventoryId int `json:"inventory_id"`
  51. RepositoryId int `json:"repository_id"`
  52. EnvironmentId int `json:"environment_id"`
  53. Playbook string `json:"playbook"`
  54. Type string `json:"type"`
  55. }
  56. type JobTemplate struct {
  57. Id int `json:"id"`
  58. ProjectId int `json:"project_id"`
  59. Name string `json:"name"`
  60. InventoryId int `json:"inventory_id"`
  61. RepositoryId int `json:"repository_id"`
  62. EnvironmentId int `json:"environment_id"`
  63. Playbook string `json:"playbook"`
  64. }
  65. type StartTaskRequest struct {
  66. TemplateID int `json:"template_id"`
  67. ProjectId int `json:"project_id"`
  68. }
  69. type StartTaskResponse struct {
  70. Id int `json:"id"`
  71. TemplateID int `json:"template_id"`
  72. Debug bool `json:"debug"`
  73. DryRun bool `json:"dry_run"`
  74. Diff bool `json:"diff"`
  75. Playbook string `json:"playbook"`
  76. Environment string `json:"environment"`
  77. Limit string `json:"limit"`
  78. }
  79. type EnvironmentVariables struct {
  80. SecretsProviderUrl string `json:"SECRETS_PROVIDER_URL"`
  81. SecretsProviderApiKey string `json:"SECRETS_PROVIDER_API_KEY"`
  82. }
  83. type AddEnvironmentRequest struct {
  84. Name string `json:"name"`
  85. ProjectID int `json:"project_id"`
  86. Password string `json:"password"`
  87. JSON string `json:"json"`
  88. Env string `json:"env"`
  89. }
  90. type EnvironmentResponse struct {
  91. Id int `json:"id"`
  92. Name string `json:"name"`
  93. ProjectID int `json:"project_id"`
  94. Password string `json:"password"`
  95. JSON string `json:"json"`
  96. Env string `json:"env"`
  97. }
  98. type ProjectsResponse struct {
  99. Id int `json:"id"`
  100. Name string `json:"name"`
  101. Created string `json:"created"`
  102. Alert bool `json:"alert"`
  103. AlertChat string `json:"alert_chat"`
  104. MaxParallelTasks int `json:"max_parallel_tasks"`
  105. }
  106. type NewProjectReqeust struct {
  107. Name string `json:"name"`
  108. Alert bool `json:"alert"`
  109. AlertChat string `json:"alert_chat"`
  110. MaxParallelTasks int `json:"max_parallel_tasks"`
  111. }
  112. type NewRepoRequest struct {
  113. Name string `json:"name"` // name of the project
  114. ProjectId int `json:"project_id"` // the numerical ID of the project as per /api/project/<project id>
  115. GitUrl string `json:"git_url"` // the URL of the git repo (SSH address)
  116. GitBranch string `json:"git_branch"` // the branch to pull down
  117. SshKeyId int `json:"ssh_key_id"` // the numerical ID of the ssh key for the repository, as per /api/project/<project id>/keys
  118. }
  119. type NewRepoResponse struct {
  120. Id int `json:"id"` // the numerical ID assigned to the repo by Semaphore
  121. Name string `json:"name"` // name of the project
  122. ProjectId int `json:"project_id"` // the numerical ID of the project as per /api/project/<project id>
  123. GitUrl string `json:"git_url"` // the URL of the git repo (SSH address)
  124. GitBranch string `json:"git_branch"` // the branch to pull down
  125. SshKeyId int `json:"ssh_key_id"` // the numerical ID of the ssh key for the repository, as per /api/project/<project id>/keys
  126. }
  127. type AddKeyRequest struct {
  128. Name string `json:"name"`
  129. Type string `json:"type"`
  130. ProjectId int `json:"project_id"`
  131. LoginPassword loginPassword `json:"login_password"`
  132. Ssh sshKeyAdd `json:"ssh"`
  133. }
  134. func (a AddKeyRequest) GetPublic() string {
  135. if a.Type == "ssh" {
  136. return a.Ssh.Login
  137. } else {
  138. return a.LoginPassword.Login
  139. }
  140. }
  141. func (a AddKeyRequest) GetSecret() string {
  142. if a.Type == "ssh" {
  143. return a.Ssh.PrivateKey
  144. } else {
  145. return a.LoginPassword.Password
  146. }
  147. }
  148. func (a AddKeyRequest) Prepare() string {
  149. b, err := json.Marshal(a)
  150. if err != nil {
  151. return err.Error()
  152. }
  153. return string(b)
  154. }
  155. func (a AddKeyRequest) GetType() string {
  156. return a.Type
  157. }
  158. type KeyItemResponse struct {
  159. Id int `json:"id"`
  160. Name string `json:"name"`
  161. Type string `json:"type"`
  162. ProjectId int `json:"project_id"`
  163. LoginPassword loginPassword `json:"login_password"`
  164. Ssh sshKeyAdd `json:"ssh"`
  165. }
  166. type NewInventoryRequest struct {
  167. Name string `json:"name"`
  168. ProjectId int `json:"project_id"`
  169. Inventory string `json:"inventory"` // This field is where the YAML inventory file gets put, as a string (not a filepath!)
  170. Type string `json:"type"`
  171. SshKeyId int `json:"ssh_key_id"`
  172. BecomeKeyId int `json:"become_key_id"`
  173. }
  174. type InventoryResponse struct {
  175. Id int `json:"id"`
  176. Inventory string `json:"inventory"`
  177. Name string `json:"name"`
  178. ProjectId int `json:"project_id"`
  179. Type string `json:"type"`
  180. SshKeyId int `json:"ssh_key_id"`
  181. BecomeKeyId int `json:"become_key_id"`
  182. }
  183. /*
  184. ####################################################################
  185. ############ IMPLEMENTING daemon.Key FOR KeyItemResponse ###########
  186. ####################################################################
  187. */
  188. func (k KeyItemResponse) GetPublic() string {
  189. return k.Ssh.Login
  190. }
  191. func (k KeyItemResponse) GetSecret() string {
  192. return k.Ssh.PrivateKey
  193. }
  194. func (k KeyItemResponse) Prepare() string {
  195. return k.Type
  196. }
  197. func (k KeyItemResponse) GetType() string {
  198. return k.Type
  199. }
  200. type loginPassword struct {
  201. Password string `json:"password"`
  202. Login string `json:"login"`
  203. }
  204. type sshKeyAdd struct {
  205. PrivateKey string `json:"private_key"`
  206. Login string `json:"login"`
  207. }
  208. /*
  209. ###################################################################
  210. ########### IMPLEMENTING THE DaemonKeyRing INTERFACE ##############
  211. ###################################################################
  212. */
  213. /*
  214. Get SSH key by its name
  215. */
  216. func (s SemaphoreConnection) GetKey(name string) (daemon.Key, error) {
  217. var key KeyItemResponse
  218. keys, err := s.GetAllKeys()
  219. if err != nil {
  220. return key, daemon.KeyRingError
  221. }
  222. for i := range keys {
  223. if keys[i].Name == name {
  224. return keys[i], nil
  225. }
  226. }
  227. return key, daemon.KeyNotFound
  228. }
  229. /*
  230. Add SSH Key to a project
  231. :param name: the name to assign the key in the project
  232. :param keyring: a daemon.DaemonKeyRing implementer that can return the API key for Semaphore
  233. :param key: a daemon.Key implementer wrapping the SSH key
  234. */
  235. func (s SemaphoreConnection) AddKey(name string, key daemon.Key) error {
  236. _, err := s.GetKeyId(name)
  237. if err == nil { // return if the key exists
  238. return nil
  239. }
  240. path := fmt.Sprintf("%s/%v/keys", ProjectPath, s.ProjectId)
  241. _, err = s.Post(path, bytes.NewReader([]byte(key.Prepare())))
  242. if err != nil {
  243. return err
  244. }
  245. return nil
  246. }
  247. /*
  248. Drop a key from the Semaphore secret store
  249. */
  250. func (s SemaphoreConnection) RemoveKey(name string) error {
  251. _, err := s.Delete(name)
  252. return err
  253. }
  254. // Return the resource name for logging purposes
  255. func (s SemaphoreConnection) Source() string {
  256. return "Semaphore Keystore"
  257. }
  258. // NewKeyRequest builder function
  259. func (s SemaphoreConnection) NewKeyRequestBuilder(name string, key daemon.Key) daemon.Key {
  260. if key.GetType() == "ssh" {
  261. return AddKeyRequest{
  262. Name: name,
  263. Type: key.GetType(),
  264. ProjectId: s.ProjectId,
  265. Ssh: sshKeyAdd{
  266. Login: key.GetPublic(),
  267. PrivateKey: key.GetSecret(),
  268. },
  269. }
  270. } else {
  271. return AddKeyRequest{
  272. Name: name,
  273. Type: key.GetType(),
  274. ProjectId: s.ProjectId,
  275. LoginPassword: loginPassword{
  276. Login: key.GetPublic(),
  277. Password: key.GetSecret(),
  278. },
  279. }
  280. }
  281. }
  282. /*
  283. Create a new semaphore client
  284. :param url: the base url of the semaphore server, without the HTTP/S prefix
  285. :param proto: either HTTP or HTTPS, depending on the server's SSL setup
  286. :param log: an io.Writer to write logfile to
  287. :param keyring: a daemon.DaemonKeyRing implementer to get the Semaphore API key from
  288. */
  289. func NewSemaphoreClient(url string, proto string, keyring daemon.DaemonKeyRing, conf *daemon.ConfigFromFile, keytagger keytags.Keytagger) SemaphoreConnection {
  290. client := &http.Client{}
  291. semaphoreBootstrap := SemaphoreConnection{Client: client, ServerUrl: url, HttpProto: proto, Keyring: keyring, KeyTagger: keytagger, Config: conf}
  292. semaphoreBootstrap.Log("Using mode ", proto)
  293. id, err := semaphoreBootstrap.GetProjectByName(YosaiProject)
  294. if err != nil {
  295. semaphoreBootstrap.Log(YosaiProject, "Not found in semaphore. Creating...")
  296. err = semaphoreBootstrap.NewProject(YosaiProject)
  297. if err != nil {
  298. semaphoreBootstrap.Log("Fatal error creating the project in semaphore: ", err.Error(), "exiting.")
  299. os.Exit(127)
  300. }
  301. id, err := semaphoreBootstrap.GetProjectByName(YosaiProject)
  302. if err != nil {
  303. semaphoreBootstrap.Log("Error finding the project: ", YosaiProject, err.Error(), "exiting.")
  304. os.Exit(127)
  305. }
  306. semaphoreBootstrap.Log(YosaiProject, "found with ID: ", fmt.Sprint(id))
  307. semaphoreBootstrap.Log("OK! Semaphore connection established.")
  308. return SemaphoreConnection{
  309. Client: client,
  310. ServerUrl: url,
  311. HttpProto: proto,
  312. ProjectId: id,
  313. Keyring: keyring,
  314. Config: conf,
  315. KeyTagger: keytagger,
  316. }
  317. }
  318. semaphoreBootstrap.Log("OK! Semaphore connection established.")
  319. return SemaphoreConnection{
  320. Client: &http.Client{},
  321. ServerUrl: url,
  322. HttpProto: proto,
  323. ProjectId: id,
  324. Keyring: keyring,
  325. Config: conf,
  326. KeyTagger: keytagger,
  327. }
  328. }
  329. // logging wrapper
  330. func (s *SemaphoreConnection) Log(msg ...string) {
  331. semMsg := []string{
  332. "SemaphoreConnection:",
  333. }
  334. semMsg = append(semMsg, msg...)
  335. s.Config.Log(semMsg...)
  336. }
  337. /*
  338. Create a new 'Project' in Semaphore
  339. :param name: the name to assign the project
  340. :param keyring: a daemon.DaemonKeyRing implementer to get the Semaphore API key from
  341. */
  342. func (s SemaphoreConnection) NewProject(name string) error {
  343. _, err := s.GetProjectByName(name)
  344. if err == nil {
  345. return nil // return nil of project already exists
  346. }
  347. var b []byte
  348. newProj := NewProjectReqeust{
  349. Name: name,
  350. Alert: false,
  351. AlertChat: "",
  352. MaxParallelTasks: 0,
  353. }
  354. b, err = json.Marshal(&newProj)
  355. if err != nil {
  356. return &SemaphoreClientError{Msg: err.Error()}
  357. }
  358. _, err = s.Post(ProjectsPath, bytes.NewReader(b))
  359. if err != nil {
  360. return err
  361. }
  362. return nil
  363. }
  364. /*
  365. Add a repository to the project designated for the Yosai service
  366. :param giturl: the url for the git repo containing the ansible scripts for VPN server config
  367. :param branch: the branch to target on the git repo
  368. */
  369. func (s SemaphoreConnection) AddRepository(giturl string, branch string) error {
  370. _, err := s.GetRepoByName(fmt.Sprintf("%s:%s", giturl, branch))
  371. if err == nil { // return if the repo exists
  372. return nil
  373. }
  374. sshKeyId, err := s.GetKeyId(s.KeyTagger.GitSshKeyname())
  375. if err != nil {
  376. return err
  377. }
  378. repoAddRequest := NewRepoRequest{
  379. Name: fmt.Sprintf("%s:%s", giturl, branch),
  380. ProjectId: s.ProjectId,
  381. GitUrl: giturl,
  382. GitBranch: branch,
  383. SshKeyId: sshKeyId,
  384. }
  385. b, err := json.Marshal(&repoAddRequest)
  386. if err != nil {
  387. return &SemaphoreClientError{Msg: err.Error()}
  388. }
  389. _, err = s.Post(fmt.Sprintf("%s/%v/repositories", ProjectPath, s.ProjectId), bytes.NewReader(b))
  390. if err != nil {
  391. return err
  392. }
  393. return nil
  394. }
  395. /*
  396. Generic POST Request to sent to the Semaphore server
  397. :param path: the path to the API to POST. Preceeding slashes will be trimmed
  398. :param body: an io.Reader implementer to use as the POST body. Must comply with application/json Content-Type
  399. */
  400. func (s SemaphoreConnection) Put(path string, body io.Reader) ([]byte, error) {
  401. var b []byte
  402. apikey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  403. if err != nil {
  404. return b, &SemaphoreClientError{Msg: err.Error()}
  405. }
  406. req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), body)
  407. if err != nil {
  408. return b, &SemaphoreClientError{Msg: err.Error()}
  409. }
  410. req.Header.Add("Authorization", apikey.Prepare())
  411. req.Header.Add("Content-Type", "application/json")
  412. resp, err := s.Client.Do(req)
  413. if err != nil {
  414. return b, &SemaphoreClientError{Msg: err.Error()}
  415. }
  416. defer resp.Body.Close()
  417. if resp.StatusCode >= 400 {
  418. return b, &SemaphoreClientError{Msg: resp.Status}
  419. }
  420. b, err = io.ReadAll(resp.Body)
  421. if err != nil {
  422. return b, &SemaphoreClientError{Msg: err.Error()}
  423. }
  424. return b, nil
  425. }
  426. /*
  427. Generic POST Request to sent to the Semaphore server
  428. :param path: the path to the API to POST. Preceeding slashes will be trimmed
  429. :param body: an io.Reader implementer to use as the POST body. Must comply with application/json Content-Type
  430. */
  431. func (s SemaphoreConnection) Post(path string, body io.Reader) ([]byte, error) {
  432. var b []byte
  433. apikey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  434. if err != nil {
  435. return b, &SemaphoreClientError{Msg: err.Error()}
  436. }
  437. req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), body)
  438. if err != nil {
  439. return b, &SemaphoreClientError{Msg: err.Error()}
  440. }
  441. req.Header.Add("Authorization", apikey.Prepare())
  442. req.Header.Add("Content-Type", "application/json")
  443. resp, err := s.Client.Do(req)
  444. if err != nil {
  445. return b, &SemaphoreClientError{Msg: err.Error()}
  446. }
  447. defer resp.Body.Close()
  448. b, err = io.ReadAll(resp.Body)
  449. if err != nil {
  450. return b, &SemaphoreClientError{Msg: err.Error()}
  451. }
  452. if resp.StatusCode >= 400 {
  453. return b, &SemaphoreClientError{Msg: resp.Status}
  454. }
  455. return b, nil
  456. }
  457. /*
  458. Agnostic GET method for calling the upstream Semaphore server
  459. :param path: the path to GET, added into the base API url
  460. */
  461. func (s SemaphoreConnection) Get(path string) ([]byte, error) {
  462. var b []byte
  463. apiKey, err := s.Keyring.GetKey(s.KeyTagger.SemaphoreApiKeyname())
  464. if err != nil {
  465. return b, &SemaphoreClientError{Msg: err.Error()}
  466. }
  467. req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s/%s", s.HttpProto, s.ServerUrl, strings.TrimPrefix(path, "/")), nil)
  468. if err != nil {
  469. return b, &SemaphoreClientError{Msg: err.Error()}
  470. }
  471. req.Header.Add("Authorization", apiKey.Prepare())
  472. resp, err := s.Client.Do(req)
  473. if err != nil {
  474. return b, &SemaphoreClientError{Msg: err.Error()}
  475. }
  476. defer resp.Body.Close()
  477. b, err = io.ReadAll(resp.Body)
  478. if err != nil {
  479. return b, &SemaphoreClientError{Msg: err.Error()}
  480. }
  481. return b, nil
  482. }
  483. /*
  484. Generic DELETE method for calling the Semaphore server
  485. */
  486. func (s SemaphoreConnection) Delete(path string) ([]byte, error) {
  487. return []byte{}, nil
  488. }
  489. /*
  490. Retrieve the projects in Semaphore
  491. :param keyring: a daemon.DaemonKeyRing implementer to get the API key from for Semaphore
  492. */
  493. func (s SemaphoreConnection) GetProjects() ([]ProjectsResponse, error) {
  494. var projectsResp []ProjectsResponse
  495. b, err := s.Get(ProjectsPath)
  496. if err != nil {
  497. return projectsResp, err
  498. }
  499. err = json.Unmarshal(b, &projectsResp)
  500. if err != nil {
  501. return projectsResp, &SemaphoreClientError{Msg: err.Error()}
  502. }
  503. return projectsResp, nil
  504. }
  505. /*
  506. Get Project by its name, and return its ID
  507. */
  508. func (s SemaphoreConnection) GetProjectByName(name string) (int, error) {
  509. projects, err := s.GetProjects()
  510. if err != nil {
  511. return 0, err
  512. }
  513. for i := range projects {
  514. if projects[i].Name == name {
  515. return projects[i].Id, nil
  516. }
  517. }
  518. return 0, &SemaphoreClientError{Msg: fmt.Sprintf("Project with name: '%s' not found.", name)}
  519. }
  520. /*
  521. Get SSH Keys from the current project
  522. */
  523. func (s SemaphoreConnection) GetAllKeys() ([]KeyItemResponse, error) {
  524. var keys []KeyItemResponse
  525. b, err := s.Get(fmt.Sprintf("%s/%v/keys", ProjectPath, s.ProjectId))
  526. if err != nil {
  527. return keys, err
  528. }
  529. err = json.Unmarshal(b, &keys)
  530. if err != nil {
  531. return keys, &SemaphoreClientError{Msg: err.Error()}
  532. }
  533. return keys, nil
  534. }
  535. /*
  536. Return a key ID from the Semaphore keystore by it's name
  537. :param keyname: the name of the key in Semaphore
  538. */
  539. func (s SemaphoreConnection) GetKeyId(keyname string) (int, error) {
  540. keys, err := s.GetAllKeys()
  541. if err != nil {
  542. return 0, err
  543. }
  544. for i := range keys {
  545. if keys[i].Name == keyname {
  546. return keys[i].Id, nil
  547. }
  548. }
  549. return 0, &KeyNotFound{Keyname: keyname}
  550. }
  551. /*
  552. Get the output of a task
  553. :param taskId: the ID of the task that was ran
  554. */
  555. func (s SemaphoreConnection) GetTaskOutput(taskId int) ([]TaskOutput, error) {
  556. var taskout []TaskOutput
  557. b, err := s.Get(fmt.Sprintf("%s/%v/tasks/%v/output", ProjectPath, s.ProjectId, taskId))
  558. if err != nil {
  559. return taskout, err
  560. }
  561. err = json.Unmarshal(b, &taskout)
  562. if err != nil {
  563. return taskout, &SemaphoreClientError{Msg: "Could not unmarshall the response from getting task output." + err.Error()}
  564. }
  565. return taskout, nil
  566. }
  567. /*
  568. Get information relating to a task
  569. :param taskId: the ID of the task that was ran
  570. */
  571. func (s SemaphoreConnection) GetTaskInfo(taskId int) (TaskInfo, error) {
  572. var taskout TaskInfo
  573. b, err := s.Get(fmt.Sprintf("%s/%v/tasks/%v", ProjectPath, s.ProjectId, taskId))
  574. if err != nil {
  575. return taskout, err
  576. }
  577. err = json.Unmarshal(b, &taskout)
  578. if err != nil {
  579. return taskout, &SemaphoreClientError{Msg: "Could not unmarshall the response from getting task output." + err.Error()}
  580. }
  581. return taskout, nil
  582. }
  583. /*
  584. Poll for task completion
  585. :param taskId: the ID of the task to be polled
  586. :param max_tries: the number of times to poll the running task before timing out
  587. */
  588. func (s SemaphoreConnection) PollTask(taskId int, max_tries int) error {
  589. var attempts int
  590. for {
  591. attempts = attempts + 1
  592. s.Log("Polling task: ", fmt.Sprint(taskId), " for ", fmt.Sprint(attempts), " times.")
  593. if attempts > max_tries {
  594. s.Log("Polling for job completion timed out after: ", fmt.Sprint(attempts), " attempts.")
  595. return &SemaphoreTimeout{Tries: attempts}
  596. }
  597. resp, err := s.GetTaskInfo(taskId)
  598. if err != nil {
  599. return err
  600. }
  601. s.Log("Job: ", fmt.Sprint(taskId), " is marked with status: ", resp.Status)
  602. if resp.Status == "success" {
  603. return nil
  604. }
  605. if resp.Status == "error" {
  606. return &SemaphoreTimeout{Tries: attempts}
  607. }
  608. time.Sleep(time.Second * 5)
  609. }
  610. }
  611. /*
  612. Add an inventory to semaphore
  613. :param hosts: a list of IP addresses to add to the inventory
  614. */
  615. func (s SemaphoreConnection) AddInventory(name string) error {
  616. _, err := s.GetInventoryByName(name)
  617. if err == nil { // Returning on nil error because that means the inventory exists
  618. return &SemaphoreClientError{Msg: "Inventory Exists! Please update rather than create a new."}
  619. }
  620. sshKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccSshPubkeySeed())
  621. if err != nil {
  622. return err
  623. }
  624. becomeKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccKeyname())
  625. if err != nil {
  626. return &SemaphoreClientError{Msg: err.Error()}
  627. }
  628. body := NewInventoryRequest{
  629. Name: name,
  630. ProjectId: s.ProjectId,
  631. Inventory: string([]byte("all:\n")),
  632. SshKeyId: sshKeyId,
  633. BecomeKeyId: becomeKeyId,
  634. Type: "static-yaml",
  635. }
  636. requestBody, err := json.Marshal(&body)
  637. if err != nil {
  638. return &SemaphoreClientError{Msg: err.Error()}
  639. }
  640. _, err = s.Post(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "inventory"), bytes.NewReader(requestBody))
  641. return err
  642. }
  643. /*
  644. Get Inventory by name and return its ID
  645. :param name: the name of the inventory to find
  646. */
  647. func (s SemaphoreConnection) GetInventoryByName(name string) (InventoryResponse, error) {
  648. var out InventoryResponse
  649. resp, err := s.GetAllInventories()
  650. if err != nil {
  651. return out, err
  652. }
  653. for i := range resp {
  654. if resp[i].Name == name {
  655. return resp[i], nil
  656. }
  657. }
  658. return out, &KeyNotFound{Keyname: name}
  659. }
  660. /*
  661. Get all inventories from Semaphore
  662. */
  663. func (s SemaphoreConnection) GetAllInventories() ([]InventoryResponse, error) {
  664. var resp []InventoryResponse
  665. b, err := s.Get(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "inventory"))
  666. if err != nil {
  667. return resp, err
  668. }
  669. err = json.Unmarshal(b, &resp)
  670. if err != nil {
  671. return resp, &SemaphoreClientError{Msg: err.Error()}
  672. }
  673. return resp, nil
  674. }
  675. /*
  676. Update an inventory
  677. */
  678. func (s SemaphoreConnection) UpdateInventory(name string, inv YamlInventory) error {
  679. sshKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccSshPubkeySeed())
  680. if err != nil {
  681. return err
  682. }
  683. becomeKeyId, err := s.GetKeyId(s.KeyTagger.VpsSvcAccKeyname())
  684. if err != nil {
  685. return err
  686. }
  687. b, err := yaml.Marshal(inv)
  688. if err != nil {
  689. return &SemaphoreClientError{Msg: "Error unmarshalling YAML inventory payload: " + err.Error()}
  690. }
  691. targetInv, err := s.GetInventoryByName(name)
  692. if err != nil {
  693. return &SemaphoreClientError{Msg: "Target inventory: " + name + " was not found."}
  694. }
  695. body := InventoryResponse{
  696. Id: targetInv.Id,
  697. Name: name,
  698. ProjectId: s.ProjectId,
  699. Inventory: string(b),
  700. SshKeyId: sshKeyId,
  701. BecomeKeyId: becomeKeyId,
  702. Type: "static-yaml",
  703. }
  704. req, err := json.Marshal(body)
  705. if err != nil {
  706. return &SemaphoreClientError{Msg: "There was an error marshalling the JSON payload: " + err.Error()}
  707. }
  708. _, err = s.Put(fmt.Sprintf("%s/%v/inventory/%v", ProjectPath, s.ProjectId, targetInv.Id), bytes.NewReader(req))
  709. return err
  710. }
  711. /*
  712. Remove host from an inventory
  713. */
  714. func (s SemaphoreConnection) RemoveHostFromInv(name string, host ...string) error {
  715. resp, err := s.GetInventoryByName(name)
  716. if err != nil {
  717. return err
  718. }
  719. var inv YamlInventory
  720. err = yaml.Unmarshal([]byte(resp.Inventory), &inv)
  721. if err != nil {
  722. return &SemaphoreClientError{Msg: "Error unmarshalling inventory from server: " + resp.Inventory + err.Error()}
  723. }
  724. for i := range host {
  725. _, ok := inv.All.Hosts[host[i]]
  726. if !ok {
  727. return &SemaphoreClientError{Msg: "Host: " + host[i] + " not found in the inventory: " + resp.Inventory}
  728. }
  729. delete(inv.All.Hosts, host[i])
  730. }
  731. return s.UpdateInventory(name, inv)
  732. }
  733. /*
  734. Add hosts to inventory
  735. */
  736. func (s SemaphoreConnection) AddHostToInv(name string, host ...daemon.VpnServer) error {
  737. resp, err := s.GetInventoryByName(name)
  738. if err != nil {
  739. return err
  740. }
  741. var inv YamlInventory
  742. err = yaml.Unmarshal([]byte(resp.Inventory), &inv)
  743. if err != nil {
  744. return &SemaphoreClientError{Msg: "Error unmarshalling inventory from server: " + resp.Inventory + err.Error()}
  745. }
  746. newHosts := s.YamlInventoryBuilder(host)
  747. for addr, host := range newHosts.All.Hosts {
  748. inv.All.Hosts[addr] = host
  749. }
  750. return s.UpdateInventory(name, inv)
  751. }
  752. /*
  753. Get a repo ID by its name
  754. :param name: the name of the repo
  755. */
  756. func (s SemaphoreConnection) GetRepoByName(name string) (int, error) {
  757. resp, err := s.GetAllRepos()
  758. if err != nil {
  759. return 0, err
  760. }
  761. for i := range resp {
  762. if resp[i].Name == name {
  763. return resp[i].Id, nil
  764. }
  765. }
  766. return 0, &KeyNotFound{Keyname: name}
  767. }
  768. /*
  769. Get all repositories from Semaphore
  770. */
  771. func (s SemaphoreConnection) GetAllRepos() ([]NewRepoResponse, error) {
  772. var resp []NewRepoResponse
  773. b, err := s.Get(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "repositories"))
  774. if err != nil {
  775. return resp, &SemaphoreClientError{Msg: err.Error()}
  776. }
  777. err = json.Unmarshal(b, &resp)
  778. if err != nil {
  779. return resp, &SemaphoreClientError{Msg: err.Error()}
  780. }
  781. return resp, nil
  782. }
  783. // Create an environment variable configuration, currently unimplemented
  784. func (s SemaphoreConnection) AddEnvironment(envVars EnvironmentVariables) error {
  785. envBytes, err := json.Marshal(envVars)
  786. if err != nil {
  787. return &SemaphoreClientError{Msg: "Couldnt unmarshall the environment variable payload: " + err.Error()}
  788. }
  789. _, err = s.GetEnvironmentId(YosaiEnvironment)
  790. if err == nil {
  791. return nil // environment exists, dont add another with same name
  792. }
  793. var body AddEnvironmentRequest
  794. body = AddEnvironmentRequest{
  795. Name: YosaiEnvironment,
  796. ProjectID: s.ProjectId,
  797. JSON: "{}",
  798. Env: string(envBytes),
  799. }
  800. b, err := json.Marshal(body)
  801. if err != nil {
  802. return &SemaphoreClientError{Msg: "couldnt marshal the JSON payload"}
  803. }
  804. _, err = s.Post(fmt.Sprintf("%s/%v/environment", ProjectPath, s.ProjectId), bytes.NewBuffer(b))
  805. return err
  806. }
  807. // Get an environment configuration ID by name.
  808. func (s SemaphoreConnection) GetEnvironmentId(name string) (int, error) {
  809. var env []EnvironmentResponse
  810. b, err := s.Get(fmt.Sprintf("%s/%v/environment", ProjectPath, s.ProjectId))
  811. if err != nil {
  812. return 0, err
  813. }
  814. err = json.Unmarshal(b, &env)
  815. if err != nil {
  816. return 0, &SemaphoreClientError{Msg: "Couldnt unmarshall the response"}
  817. }
  818. for i := range env {
  819. if env[i].Name == name {
  820. return env[i].Id, nil
  821. }
  822. }
  823. return 0, &KeyNotFound{Keyname: "Couldnt find environment: " + name}
  824. }
  825. /*
  826. Add job template to the Yosai project on Semaphore
  827. :param playbook: the name of the playbook file
  828. :param repoName: the name of the repo that the playbook belongs to
  829. */
  830. func (s SemaphoreConnection) AddJobTemplate(playbook string, repoName string) error {
  831. _, err := s.JobTemplateByName(YosaiVpnRotationJob)
  832. if err == nil {
  833. return nil // return nil because template exists
  834. }
  835. repoId, err := s.GetRepoByName(repoName)
  836. if err != nil {
  837. return err
  838. }
  839. InventoryItem, err := s.GetInventoryByName(YosaiServerInventory)
  840. if err != nil {
  841. return err
  842. }
  843. envId, err := s.GetEnvironmentId(YosaiEnvironment)
  844. if err != nil {
  845. return err
  846. }
  847. templ := NewTemplateRequest{
  848. ProjectId: s.ProjectId,
  849. Name: YosaiVpnRotationJob,
  850. InventoryId: InventoryItem.Id,
  851. RepositoryId: repoId,
  852. EnvironmentId: envId,
  853. Playbook: playbook,
  854. Type: "",
  855. }
  856. b, err := json.Marshal(templ)
  857. if err != nil {
  858. return &SemaphoreClientError{Msg: err.Error()}
  859. }
  860. b, err = s.Post(fmt.Sprintf("%s/%v/%s", ProjectPath, s.ProjectId, "templates"), bytes.NewReader(b))
  861. if err != nil {
  862. return &SemaphoreClientError{Msg: fmt.Sprintf("Error: %s\nServer Response: %s", err.Error(), string(b))}
  863. }
  864. return nil
  865. }
  866. /*
  867. Start a task in Semaphore by the template name
  868. :param name: the name of the job template to start
  869. */
  870. func (s SemaphoreConnection) StartJob(name string) (StartTaskResponse, error) {
  871. var resp StartTaskResponse
  872. template, err := s.JobTemplateByName(name)
  873. if err != nil {
  874. return resp, &SemaphoreClientError{Msg: "Could not start job template: " + name + "Error: " + err.Error()}
  875. }
  876. var jobReq StartTaskRequest
  877. jobReq = StartTaskRequest{
  878. TemplateID: template.Id,
  879. ProjectId: s.ProjectId,
  880. }
  881. b, err := json.Marshal(&jobReq)
  882. if err != nil {
  883. return resp, &SemaphoreClientError{Msg: "Couldnt marshal data into byte array: " + err.Error()}
  884. }
  885. rb, err := s.Post(fmt.Sprintf("%s/%v/tasks", ProjectPath, s.ProjectId), bytes.NewReader(b))
  886. if err != nil {
  887. return resp, err
  888. }
  889. err = json.Unmarshal(rb, &resp)
  890. if err != nil {
  891. return resp, &SemaphoreClientError{Msg: "Couldnt unmarshal the response from semaphore: " + err.Error()}
  892. }
  893. return resp, nil
  894. }
  895. /*
  896. Get a job template ID by name
  897. :param name: the name of the job template ID
  898. */
  899. func (s SemaphoreConnection) GetAllTemplates() ([]JobTemplate, error) {
  900. var jobs []JobTemplate
  901. resp, err := s.Get(fmt.Sprintf("%s/%v/templates", ProjectPath, s.ProjectId))
  902. if err != nil {
  903. return jobs, err
  904. }
  905. err = json.Unmarshal(resp, &jobs)
  906. if err != nil {
  907. return jobs, &SemaphoreClientError{Msg: "Error unmarshalling payload response: " + err.Error()}
  908. }
  909. return jobs, nil
  910. }
  911. /*
  912. Bootstrap the Semaphore environment
  913. */
  914. /*
  915. Get a job template ID by name
  916. :param name: the name of the job template ID
  917. */
  918. func (s SemaphoreConnection) JobTemplateByName(name string) (JobTemplate, error) {
  919. var job JobTemplate
  920. jobs, err := s.GetAllTemplates()
  921. if err != nil {
  922. return job, err
  923. }
  924. for i := range jobs {
  925. if jobs[i].Name == name {
  926. return jobs[i], nil
  927. }
  928. }
  929. return job, &SemaphoreClientError{Msg: "Job with name" + name + "not found"}
  930. }
  931. /*
  932. ##########################################################
  933. ################## DAEMON ROUTE HANDLERS #################
  934. ##########################################################
  935. */
  936. type SemaphoreRequest struct {
  937. Target string `json:"target"`
  938. }
  939. /*
  940. Wrapping the functioanlity of the keyring bootstrapper for top level cleanliness
  941. */
  942. func (s SemaphoreConnection) keyBootstrapper() daemon.SockMessage {
  943. reqKeys := s.KeyTagger.GetAnsibleKeys()
  944. for i := range reqKeys {
  945. kn := reqKeys[i]
  946. key, err := s.Keyring.GetKey(kn)
  947. if err != nil {
  948. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  949. }
  950. err = s.AddKey(kn, s.NewKeyRequestBuilder(kn, key))
  951. if err != nil {
  952. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  953. }
  954. }
  955. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Daemon keyring successfuly bootstrapped."))
  956. }
  957. /*
  958. Wrapping the functionality of the Project bootstrapper for top level cleanliness
  959. */
  960. func (s SemaphoreConnection) projectBootstrapper() daemon.SockMessage {
  961. err := s.NewProject(YosaiProject)
  962. if err != nil {
  963. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  964. }
  965. err = s.AddRepository(s.Config.Repo(), s.Config.Branch())
  966. if err != nil {
  967. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  968. }
  969. hashiKey, err := s.Keyring.GetKey(s.KeyTagger.HashicorpVaultKeyname())
  970. if err != nil {
  971. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  972. }
  973. err = s.AddEnvironment(EnvironmentVariables{SecretsProviderUrl: s.Config.SecretsBackendUrl(), SecretsProviderApiKey: hashiKey.GetSecret()})
  974. if err != nil {
  975. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  976. }
  977. err = s.AddJobTemplate(s.Config.PlaybookName(), fmt.Sprintf("%s:%s", s.Config.Repo(), s.Config.Branch()))
  978. if err != nil {
  979. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  980. }
  981. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Project successfuly bootstrapped."))
  982. }
  983. /*
  984. Wrapping the inventory bootstrap functionality for top level cleanliness
  985. */
  986. func (s SemaphoreConnection) inventoryBootstrapper() daemon.SockMessage {
  987. err := s.AddInventory(YosaiServerInventory)
  988. if err != nil {
  989. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  990. }
  991. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Inventory successfuly bootstrapped."))
  992. }
  993. func (s SemaphoreConnection) BootstrapHandler(msg daemon.SockMessage) daemon.SockMessage {
  994. var req SemaphoreRequest
  995. err := json.Unmarshal(msg.Body, &req)
  996. if err != nil {
  997. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  998. }
  999. switch req.Target {
  1000. case "keys":
  1001. return s.keyBootstrapper()
  1002. case "project":
  1003. return s.projectBootstrapper()
  1004. case "inventory":
  1005. return s.inventoryBootstrapper()
  1006. case "all":
  1007. bootstrapFuncs := []func() daemon.SockMessage{
  1008. s.keyBootstrapper,
  1009. s.inventoryBootstrapper,
  1010. s.projectBootstrapper,
  1011. }
  1012. successMsg := ""
  1013. for i := range bootstrapFuncs {
  1014. call := bootstrapFuncs[i]
  1015. resp := call()
  1016. if resp.StatusCode != daemon.REQUEST_OK {
  1017. return resp
  1018. }
  1019. successMsg = successMsg + resp.StatusMsg + "\n"
  1020. }
  1021. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte(successMsg))
  1022. default:
  1023. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolved Method."))
  1024. }
  1025. }
  1026. /*
  1027. Router for handling all stuff relating to Projects
  1028. :param msg: a daemon.SockMessage with request info
  1029. */
  1030. func (s SemaphoreConnection) ProjectHandler(msg daemon.SockMessage) daemon.SockMessage {
  1031. switch msg.Method {
  1032. case "add":
  1033. var req SemaphoreRequest
  1034. err := json.Unmarshal(msg.Body, &req)
  1035. if err != nil {
  1036. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1037. }
  1038. err = s.NewProject(req.Target)
  1039. if err != nil {
  1040. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1041. }
  1042. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Project: "+req.Target+" successfully added."))
  1043. case "show":
  1044. proj, err := s.GetProjects()
  1045. if err != nil {
  1046. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1047. }
  1048. b, err := json.MarshalIndent(proj, " ", " ")
  1049. if err != nil {
  1050. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1051. }
  1052. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1053. default:
  1054. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolved Method."))
  1055. }
  1056. }
  1057. /*
  1058. handler to wrap all functions relating to Tasks
  1059. :param msg: a daemon.SockMessage that contains the request information
  1060. */
  1061. func (s SemaphoreConnection) TaskHandler(msg daemon.SockMessage) daemon.SockMessage {
  1062. var req SemaphoreRequest
  1063. err := json.Unmarshal(msg.Body, &req)
  1064. if err != nil {
  1065. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1066. }
  1067. switch msg.Method {
  1068. case "run":
  1069. resp, err := s.StartJob(req.Target)
  1070. if err != nil {
  1071. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1072. }
  1073. b, err := json.MarshalIndent(resp, " ", " ")
  1074. if err != nil {
  1075. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1076. }
  1077. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1078. case "show":
  1079. taskid, err := strconv.Atoi(req.Target)
  1080. if err != nil {
  1081. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1082. }
  1083. taskout, err := s.GetTaskOutput(taskid)
  1084. if err != nil {
  1085. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1086. }
  1087. b, err := json.MarshalIndent(taskout, " ", " ")
  1088. if err != nil {
  1089. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1090. }
  1091. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1092. case "poll":
  1093. taskId, err := strconv.Atoi(req.Target)
  1094. if err != nil {
  1095. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_TIMEOUT, []byte(err.Error()))
  1096. }
  1097. err = s.PollTask(taskId, 60)
  1098. if err != nil {
  1099. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_TIMEOUT, []byte(err.Error()))
  1100. }
  1101. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, []byte("Task: "+req.Target+" completed."))
  1102. default:
  1103. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolved Method."))
  1104. }
  1105. }
  1106. /*
  1107. Handles all of the requests relating to Hosts
  1108. :param msg: a daemon.SockMessage containing all of the request info
  1109. */
  1110. func (s SemaphoreConnection) HostHandler(msg daemon.SockMessage) daemon.SockMessage {
  1111. var req SemaphoreRequest
  1112. err := json.Unmarshal(msg.Body, &req)
  1113. if err != nil {
  1114. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1115. }
  1116. switch msg.Method {
  1117. case "add":
  1118. hosts := strings.Split(strings.Trim(req.Target, ","), ",")
  1119. vpnHosts := []daemon.VpnServer{}
  1120. for i := range hosts {
  1121. server, err := s.Config.GetServer(hosts[i])
  1122. if err != nil {
  1123. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1124. }
  1125. vpnHosts = append(vpnHosts, server)
  1126. }
  1127. err := s.AddHostToInv(YosaiServerInventory, vpnHosts...)
  1128. if err != nil {
  1129. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1130. }
  1131. return *daemon.NewSockMessage(daemon.MsgRequest, daemon.REQUEST_OK, []byte(fmt.Sprintf("Host: %v added to the inventory", hosts)))
  1132. case "delete":
  1133. hosts := strings.Split(strings.Trim(req.Target, ","), ",")
  1134. err := s.RemoveHostFromInv(YosaiServerInventory, hosts...)
  1135. if err != nil {
  1136. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1137. }
  1138. return *daemon.NewSockMessage(daemon.MsgRequest, daemon.REQUEST_OK, []byte(fmt.Sprintf("Host: %v removed from the inventory", hosts)))
  1139. case "show":
  1140. inv, err := s.GetAllInventories()
  1141. if err != nil {
  1142. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1143. }
  1144. b, err := json.Marshal(inv)
  1145. if err != nil {
  1146. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_FAILED, []byte(err.Error()))
  1147. }
  1148. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_OK, b)
  1149. default:
  1150. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolved Method."))
  1151. }
  1152. }
  1153. /*
  1154. Implementing the router interface
  1155. :param msg: a daemon.SockMessage containing the request data
  1156. */
  1157. func (s SemaphoreConnection) SemaphoreRouter(msg daemon.SockMessage) daemon.SockMessage {
  1158. switch msg.Target {
  1159. case "bootstrap":
  1160. return s.BootstrapHandler(msg)
  1161. case "project":
  1162. return s.ProjectHandler(msg)
  1163. case "task":
  1164. return s.TaskHandler(msg)
  1165. case "hosts":
  1166. return s.HostHandler(msg)
  1167. default:
  1168. return *daemon.NewSockMessage(daemon.MsgResponse, daemon.REQUEST_UNRESOLVED, []byte("Unresolved Method."))
  1169. }
  1170. }
  1171. /*
  1172. ######################################################
  1173. ############# YAML INVENTORY STRUCTS #################
  1174. ######################################################
  1175. */
  1176. type YamlInventory struct {
  1177. All yamlInvAll `yaml:"all"`
  1178. }
  1179. type yamlInvAll struct {
  1180. Hosts map[string]yamlVars `yaml:"hosts"`
  1181. }
  1182. type yamlVars struct {
  1183. AnsibleSshCommonArgs string `yaml:"ansible_ssh_common_args"`
  1184. MachineType string `yaml:"machine_type"`
  1185. MachineSubType string `yaml:"machine_subtype"`
  1186. VpnNetworkAddress string `yaml:"vpn_network_address"`
  1187. VpnServerPort int `yaml:"vpn_server_port"`
  1188. Clients map[string]yamlVpnClient `yaml:"clients"`
  1189. SecretsProvider string `yaml:"secrets_provider"`
  1190. VpnNetMask int `yaml:"vpn_netmask"`
  1191. Name string `yaml:"name"`
  1192. }
  1193. type yamlVpnClient struct {
  1194. Name string `yaml:"name"`
  1195. Ipv4 string `yaml:"ipv4"`
  1196. Pubkey string `yaml:"pubkey"`
  1197. }
  1198. /*
  1199. YAML inventory builder function
  1200. :param hosts: a list of host IP addresses to add to the VPN server inventory
  1201. */
  1202. func (s SemaphoreConnection) YamlInventoryBuilder(hosts []daemon.VpnServer) YamlInventory {
  1203. hostmap := map[string]yamlVars{}
  1204. clientmap := map[string]yamlVpnClient{}
  1205. clients := s.Config.VpnClients()
  1206. for i := range clients {
  1207. client := clients[i]
  1208. clientmap[client.Name] = yamlVpnClient{Name: client.Name, Ipv4: client.VpnIpv4.String(), Pubkey: client.Pubkey}
  1209. }
  1210. for i := range hosts {
  1211. server := hosts[i]
  1212. hostmap[hosts[i].WanIpv4] = yamlVars{
  1213. AnsibleSshCommonArgs: "-o StrictHostKeyChecking=no",
  1214. MachineType: "vpn",
  1215. MachineSubType: "server",
  1216. VpnNetworkAddress: server.VpnIpv4.String(),
  1217. VpnServerPort: server.Port,
  1218. Clients: clientmap,
  1219. SecretsProvider: s.Config.SecretsBackend(),
  1220. VpnNetMask: s.Config.Service.VpnMask,
  1221. Name: server.Name}
  1222. }
  1223. return YamlInventory{
  1224. All: yamlInvAll{
  1225. Hosts: hostmap,
  1226. },
  1227. }
  1228. }
  1229. /*
  1230. ##########################################
  1231. ################ ERRORS ##################
  1232. ##########################################
  1233. */
  1234. type SemaphoreClientError struct {
  1235. Msg string
  1236. }
  1237. // Implementing error interface
  1238. func (s *SemaphoreClientError) Error() string {
  1239. return fmt.Sprintf("There was an error with the call to the semaphore server: '%s'", s.Msg)
  1240. }
  1241. type KeyNotFound struct{ Keyname string }
  1242. func (k *KeyNotFound) Error() string {
  1243. return fmt.Sprintf("Key '%s' was not found in the Semaphore Keystore", k.Keyname)
  1244. }
  1245. type SemaphoreTimeout struct {
  1246. Tries int
  1247. }
  1248. func (s *SemaphoreTimeout) Error() string {
  1249. return "Semaphore job execution poll timed out after: " + fmt.Sprint(s.Tries) + " calls to the server."
  1250. }