main.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. //
  2. //
  3. // @Author: F.Michel
  4. // @github: https://github.com/phpdevcommunity
  5. package main
  6. import (
  7. "bufio"
  8. "bytes"
  9. "crypto/rand"
  10. "fmt"
  11. "io"
  12. "log"
  13. "net"
  14. "os"
  15. "os/exec"
  16. "path/filepath"
  17. "strconv"
  18. "strings"
  19. "github.com/pterm/pterm"
  20. "github.com/urfave/cli/v2"
  21. )
  22. var (
  23. workflows []Workflow
  24. dockerComposeCommand = ""
  25. )
  26. func main() {
  27. currentDir := getCurrentDir()
  28. wfFiles, err := getWfFiles(currentDir)
  29. if err != nil {
  30. fmt.Println(err)
  31. }
  32. if len(wfFiles) == 0 {
  33. fmt.Println("No workflow files found")
  34. }
  35. workflows = make([]Workflow, 0)
  36. for _, wfFile := range wfFiles {
  37. wfs := ParseContentToWorkFlowStruct(wfFile)
  38. for _, wf := range wfs {
  39. if len(wf.Lines) == 0 {
  40. continue
  41. }
  42. workflows = append(workflows, wf)
  43. }
  44. }
  45. Commands := []*cli.Command{}
  46. values := InitDefaultVariables()
  47. for _, wf := range workflows {
  48. Commands = append(Commands, &cli.Command{
  49. Name: wf.Name,
  50. Usage: wf.Comment,
  51. Action: func(c *cli.Context) error {
  52. executeWorkflow(wf, values)
  53. return nil
  54. },
  55. })
  56. }
  57. app := &cli.App{
  58. Commands: Commands,
  59. }
  60. if err := app.Run(os.Args); err != nil {
  61. log.Fatal(err)
  62. }
  63. }
  64. func executeWorkflow(wf Workflow, values *map[string]string) {
  65. for _, line := range wf.Lines {
  66. executeLine(line, values)
  67. }
  68. }
  69. func FileExists(filename string) bool {
  70. _, err := os.Stat(filename)
  71. if err != nil && os.IsNotExist(err) {
  72. return false
  73. }
  74. return true
  75. }
  76. func Touch(filename string) (bool, error) {
  77. fd, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0666)
  78. if err != nil {
  79. return false, err
  80. }
  81. fd.Close()
  82. return true, nil
  83. }
  84. func executeLine(lineOriginal string, values *map[string]string) {
  85. if lineOriginal == "" || strings.HasPrefix(lineOriginal, "#") {
  86. return
  87. }
  88. lineOriginal = ResolveVariables(*values, lineOriginal)
  89. actionOriginal := strings.Split(lineOriginal, " ")[0]
  90. action := strings.ToLower(actionOriginal)
  91. line := strings.Replace(lineOriginal, actionOriginal, action, 1)
  92. if action == "set" {
  93. line = strings.TrimPrefix(line, "set ")
  94. parts := strings.Split(line, "=")
  95. if len(parts) < 2 {
  96. pterm.Error.WithFatal().Println("Invalid set command")
  97. }
  98. if parts[0] == "" || parts[1] == "" {
  99. pterm.Error.WithFatal().Println("Invalid set command")
  100. }
  101. (*values)[parts[0]] = parts[1]
  102. pterm.Info.Printfln("Variable %s set to %s", parts[0], (*values)[parts[0]])
  103. return
  104. } else if strings.HasPrefix(line, "#") {
  105. return
  106. } else if action == "run" {
  107. line = strings.TrimPrefix(line, "run ")
  108. run(line, true)
  109. return
  110. } else if action == "echo" {
  111. line = strings.TrimPrefix(line, "echo ")
  112. fmt.Println(line)
  113. return
  114. } else if action == "exit" {
  115. os.Exit(0)
  116. } else if action == "touch" {
  117. line = strings.TrimPrefix(line, "touch ")
  118. fileToCreate := strings.TrimSpace(line)
  119. if fileToCreate == "" {
  120. pterm.Error.WithFatal().Println("Invalid touch command")
  121. }
  122. if FileExists(fileToCreate) {
  123. pterm.Info.Printfln("%s already exists, skipping...", fileToCreate)
  124. return
  125. }
  126. Touch(line)
  127. fmt.Println(pterm.FgGreen.Sprintf("%s created", line))
  128. return
  129. } else if action == "copy" || action == "cp" {
  130. line = strings.TrimPrefix(line, "copy ")
  131. parts := strings.Split(line, " ")
  132. if len(parts) < 2 {
  133. pterm.Error.WithFatal().Println("Invalid copy command")
  134. }
  135. fileOrFolder := parts[0]
  136. destination := parts[1]
  137. if !FileExists(fileOrFolder) {
  138. pterm.Error.WithFatal().Printfln("%s not found", fileOrFolder)
  139. }
  140. if FileExists(destination) {
  141. pterm.Info.Printfln("%s already exists, skipping...", destination)
  142. return
  143. }
  144. Copy(fileOrFolder, destination)
  145. fmt.Println(pterm.FgGreen.Sprintf("%s copied to %s", fileOrFolder, destination))
  146. return
  147. } else if action == "mkdir" {
  148. folderName := strings.Split(line, " ")[1]
  149. if folderName == "" {
  150. pterm.Error.WithFatal().Println("Invalid mkdir command")
  151. }
  152. if FileExists(folderName) {
  153. pterm.Info.Printfln("Folder %s already exists, skipping...", folderName)
  154. return
  155. }
  156. err := os.MkdirAll(folderName, os.ModePerm)
  157. if err != nil {
  158. pterm.Error.WithFatal().Println(err)
  159. }
  160. fmt.Println(pterm.FgGreen.Sprintf("Folder %s created", folderName))
  161. return
  162. } else if action == "set_permissions" {
  163. args := strings.Fields(line)
  164. if len(args) < 3 {
  165. pterm.Error.WithFatal().Printf("Invalid set_permissions command: %s", line)
  166. }
  167. folderName := args[1]
  168. permissions, err := strconv.ParseUint(args[2], 8, 32) // Use ParseUint instead of Atoi
  169. if err != nil {
  170. pterm.Error.WithFatal().Printf("Invalid permissions: %s", args[2])
  171. }
  172. err = os.Chmod(folderName, os.FileMode(permissions))
  173. if err != nil {
  174. pterm.Error.WithFatal().Printf("Failed to set permissions for folder/file %s: %s", folderName, err)
  175. }
  176. fmt.Println(pterm.FgGreen.Sprintf("Permissions 0%o set for folder/file %s", permissions, folderName))
  177. return
  178. } else if action == "sync_time" {
  179. fmt.Println("Checking time...")
  180. run("date", false)
  181. return
  182. } else if action == "docker_compose" {
  183. args := strings.Fields(line)
  184. if len(args) < 2 {
  185. pterm.Error.WithFatal().Printf("Invalid execute command: %s", line)
  186. }
  187. dockerComposeExec := args[1:]
  188. dockerCompose := GetDockerComposeCommand()
  189. run(fmt.Sprintf("%s %s", dockerCompose, strings.Join(dockerComposeExec, " ")), true)
  190. return
  191. } else if action == "wf" {
  192. args := strings.Fields(line)
  193. if len(args) < 2 {
  194. pterm.Error.WithFatal().Printf("Invalid wf command: %s", line)
  195. }
  196. WorkflowName := args[1]
  197. for _, wf := range workflows {
  198. if wf.Name == WorkflowName {
  199. pterm.Description.Printfln("Executing workflow: %s", WorkflowName)
  200. executeWorkflow(wf, values)
  201. break
  202. }
  203. }
  204. return
  205. }
  206. litsOfNotifiesOptions := []string{
  207. "notify_success",
  208. "notify_error",
  209. "notify_warning",
  210. "notify_info",
  211. "notify",
  212. }
  213. for _, option := range litsOfNotifiesOptions {
  214. if action == option {
  215. message := strings.TrimPrefix(line, option)
  216. message = strings.TrimSpace(message)
  217. message = strings.TrimPrefix(message, "\"")
  218. message = strings.TrimSuffix(message, "\"")
  219. if option == "notify" {
  220. fmt.Println(message)
  221. } else if option == "notify_success" {
  222. pterm.Success.Println(message)
  223. } else if option == "notify_error" {
  224. pterm.Error.Println(message)
  225. } else if option == "notify_warning" {
  226. pterm.Warning.Println(message)
  227. } else if option == "notify_info" {
  228. pterm.Info.Println(message)
  229. }
  230. return
  231. // pterm.DefaultBasicText.Println(message)
  232. }
  233. }
  234. pterm.Error.WithFatal().Println("Unknown command or invalid syntax : " + line)
  235. }
  236. func run(line string, printCommand bool) {
  237. line = strings.TrimSpace(line)
  238. if line == "" {
  239. return
  240. }
  241. if printCommand {
  242. pterm.DefaultBasicText.Println(line)
  243. }
  244. args := strings.Split(line, " ")
  245. cmd := exec.Command(args[0], args[1:]...)
  246. cmd.Env = os.Environ()
  247. var stdoutBuf, stderrBuf bytes.Buffer
  248. cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
  249. cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
  250. err := cmd.Run()
  251. if err != nil {
  252. pterm.Error.Println("Error :", err)
  253. os.Exit(1)
  254. }
  255. }
  256. func getWfFiles(currentDir string) ([]string, error) {
  257. return filepath.Glob(filepath.Join(currentDir, "*.wf"))
  258. }
  259. func getCurrentDir() string {
  260. dir, _ := os.Getwd()
  261. return dir
  262. }
  263. func FileGetContents(filename string) string {
  264. data, err := os.ReadFile(filename)
  265. if err != nil {
  266. panic(err)
  267. }
  268. return string(data)
  269. }
  270. func Copy(source string, dest string) (bool, error) {
  271. fd1, err := os.Open(source)
  272. if err != nil {
  273. return false, err
  274. }
  275. defer fd1.Close()
  276. fd2, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE, 0644)
  277. if err != nil {
  278. return false, err
  279. }
  280. defer fd2.Close()
  281. _, e := io.Copy(fd2, fd1)
  282. if e != nil {
  283. return false, e
  284. }
  285. return true, nil
  286. }
  287. func ParseContentToWorkFlowStruct(filename string) map[string]Workflow {
  288. workflowsMap := map[string]Workflow{}
  289. file, err := os.Open(filename)
  290. if err != nil {
  291. panic(err)
  292. }
  293. defer file.Close()
  294. scanner := bufio.NewScanner(file)
  295. currentWorkflow := Workflow{
  296. Name: "main",
  297. Comment: "",
  298. }
  299. workflowsMap[currentWorkflow.Name] = currentWorkflow
  300. for scanner.Scan() {
  301. line := scanner.Text()
  302. line = strings.TrimSpace(line)
  303. if line == "" {
  304. continue
  305. }
  306. if strings.HasPrefix(line, "#") {
  307. continue
  308. }
  309. workflowName := currentWorkflow.Name
  310. comment := currentWorkflow.Comment
  311. if strings.HasPrefix(line, "[") {
  312. cursor := 0
  313. findWorkflowName := false
  314. for i := 0; i < len(line); i++ {
  315. if line[i] == ']' {
  316. workflowName = strings.TrimPrefix(line[:i], "[")
  317. cursor = i
  318. findWorkflowName = true
  319. break
  320. }
  321. }
  322. if !findWorkflowName {
  323. continue
  324. }
  325. afterWorkflowNameLine := line[cursor+1:]
  326. afterWorkflowNameLine = strings.TrimSpace(afterWorkflowNameLine)
  327. line = ""
  328. if strings.HasPrefix(afterWorkflowNameLine, "#") {
  329. comment = strings.TrimPrefix(afterWorkflowNameLine, "#")
  330. comment = strings.TrimSpace(comment)
  331. }
  332. }
  333. wf, ok := workflowsMap[workflowName]
  334. if !ok {
  335. wf = Workflow{
  336. Name: workflowName,
  337. Comment: comment,
  338. }
  339. workflowsMap[workflowName] = wf
  340. }
  341. wf.Lines = append(wf.Lines, line)
  342. workflowsMap[workflowName] = wf
  343. currentWorkflow = wf
  344. currentWorkflow.Lines = append(currentWorkflow.Lines, line)
  345. }
  346. if err := scanner.Err(); err != nil {
  347. panic(err)
  348. }
  349. return workflowsMap
  350. }
  351. func InitDefaultVariables() *map[string]string {
  352. values := map[string]string{}
  353. values["IP_LOCAL"] = GetLocalIP()
  354. values["CURRENT_PATH"] = getCurrentDir()
  355. return &values
  356. }
  357. func GetLocalIP() string {
  358. // Récupère toutes les interfaces réseau
  359. interfaces, err := net.Interfaces()
  360. if err != nil {
  361. return ""
  362. }
  363. for _, i := range interfaces {
  364. // Récupère toutes les adresses de chaque interface
  365. addrs, err := i.Addrs()
  366. if err != nil {
  367. return ""
  368. }
  369. for _, addr := range addrs {
  370. var ip net.IP
  371. // Vérifie si l'adresse est de type IP
  372. switch v := addr.(type) {
  373. case *net.IPNet:
  374. ip = v.IP
  375. case *net.IPAddr:
  376. ip = v.IP
  377. }
  378. // Filtre pour obtenir une adresse IPv4 non-loopback
  379. if ip != nil && ip.IsLoopback() == false && ip.To4() != nil {
  380. return ip.String()
  381. }
  382. }
  383. }
  384. return ""
  385. }
  386. func TokenGenerator(size int) string {
  387. b := make([]byte, size)
  388. rand.Read(b)
  389. return fmt.Sprintf("%x", b)
  390. }
  391. func ResolveVariables(values map[string]string, line string) string {
  392. for key, value := range values {
  393. line = strings.ReplaceAll(line, "${"+key+"}", value)
  394. }
  395. line = strings.ReplaceAll(line, "${GENERATE_SECRET}", TokenGenerator(32))
  396. return line
  397. }
  398. func GetDockerComposeCommand() string {
  399. if dockerComposeCommand != "" {
  400. return dockerComposeCommand
  401. }
  402. cmd := exec.Command("docker", "compose", "--version")
  403. cmd.Env = os.Environ()
  404. _, err := cmd.Output()
  405. if err != nil {
  406. dockerComposeCommand = "docker-compose"
  407. } else {
  408. dockerComposeCommand = "docker compose"
  409. }
  410. return dockerComposeCommand
  411. }
  412. type Workflow struct {
  413. Name string
  414. Comment string
  415. Lines []string
  416. }