ruby.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. package buildpacks
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "sync"
  9. "github.com/google/go-github/v41/github"
  10. "github.com/xanzy/go-gitlab"
  11. )
  12. type rubyRuntime struct {
  13. wg sync.WaitGroup
  14. }
  15. func NewRubyRuntime() Runtime {
  16. return &rubyRuntime{}
  17. }
  18. func (runtime *rubyRuntime) detectPuma(gemfileContent string, results chan struct {
  19. string
  20. bool
  21. },
  22. ) {
  23. pumaFound := false
  24. quotes := `["']`
  25. pumaRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %spuma%s`, quotes, quotes))
  26. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  27. for scanner.Scan() {
  28. line := []byte(scanner.Text())
  29. if pumaRe.Match(line) {
  30. pumaFound = true
  31. break
  32. }
  33. }
  34. if pumaFound {
  35. results <- struct {
  36. string
  37. bool
  38. }{puma, true}
  39. }
  40. runtime.wg.Done()
  41. }
  42. func (runtime *rubyRuntime) detectThin(gemfileContent string, results chan struct {
  43. string
  44. bool
  45. },
  46. ) {
  47. thinFound := false
  48. quotes := `["']`
  49. thinRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %sthin%s`, quotes, quotes))
  50. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  51. for scanner.Scan() {
  52. line := []byte(scanner.Text())
  53. if thinRe.Match(line) {
  54. thinFound = true
  55. break
  56. }
  57. }
  58. if thinFound {
  59. results <- struct {
  60. string
  61. bool
  62. }{thin, true}
  63. }
  64. runtime.wg.Done()
  65. }
  66. func (runtime *rubyRuntime) detectUnicorn(gemfileContent string, results chan struct {
  67. string
  68. bool
  69. },
  70. ) {
  71. unicornFound := false
  72. quotes := `["']`
  73. unicornRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %sunicorn%s`, quotes, quotes))
  74. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  75. for scanner.Scan() {
  76. line := []byte(scanner.Text())
  77. if unicornRe.Match(line) {
  78. unicornFound = true
  79. break
  80. }
  81. }
  82. if unicornFound {
  83. results <- struct {
  84. string
  85. bool
  86. }{unicorn, true}
  87. }
  88. runtime.wg.Done()
  89. }
  90. func (runtime *rubyRuntime) detectPassenger(gemfileContent string, results chan struct {
  91. string
  92. bool
  93. },
  94. ) {
  95. passengerFound := false
  96. quotes := `["']`
  97. passengerRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %spassenger%s`, quotes, quotes))
  98. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  99. for scanner.Scan() {
  100. line := []byte(scanner.Text())
  101. if passengerRe.Match(line) {
  102. passengerFound = true
  103. break
  104. }
  105. }
  106. if passengerFound {
  107. results <- struct {
  108. string
  109. bool
  110. }{passenger, true}
  111. }
  112. runtime.wg.Done()
  113. }
  114. func (runtime *rubyRuntime) detectRackupGithub(
  115. client *github.Client, owner, name string,
  116. repoContentOptions github.RepositoryContentGetOptions, results chan struct {
  117. string
  118. bool
  119. },
  120. ) {
  121. fileContent, _, _, err := client.Repositories.GetContents(context.Background(),
  122. owner, name, "Gemfile.lock", &repoContentOptions)
  123. if err != nil {
  124. runtime.wg.Done()
  125. return
  126. }
  127. gemfileLockContent, err := fileContent.GetContent()
  128. if err != nil {
  129. runtime.wg.Done()
  130. return
  131. }
  132. rackFound := false
  133. scanner := bufio.NewScanner(strings.NewReader(gemfileLockContent))
  134. for scanner.Scan() {
  135. if strings.TrimSpace(scanner.Text()) == "GEM" {
  136. for scanner.Scan() {
  137. if strings.Contains(scanner.Text(), "rack") {
  138. rackFound = true
  139. break
  140. }
  141. }
  142. }
  143. }
  144. if rackFound {
  145. results <- struct {
  146. string
  147. bool
  148. }{rackup, true}
  149. }
  150. runtime.wg.Done()
  151. }
  152. func (runtime *rubyRuntime) detectRackupGitlab(
  153. client *gitlab.Client, repoPath, ref string, results chan struct {
  154. string
  155. bool
  156. },
  157. ) {
  158. fileContent, _, err := client.RepositoryFiles.GetRawFile(
  159. repoPath, "Gemfile.lock", &gitlab.GetRawFileOptions{
  160. Ref: gitlab.String(ref),
  161. })
  162. if err != nil {
  163. runtime.wg.Done()
  164. return
  165. }
  166. gemfileLockContent := string(fileContent)
  167. rackFound := false
  168. scanner := bufio.NewScanner(strings.NewReader(gemfileLockContent))
  169. for scanner.Scan() {
  170. if strings.TrimSpace(scanner.Text()) == "GEM" {
  171. for scanner.Scan() {
  172. if strings.Contains(scanner.Text(), "rack") {
  173. rackFound = true
  174. break
  175. }
  176. }
  177. }
  178. }
  179. if rackFound {
  180. results <- struct {
  181. string
  182. bool
  183. }{rackup, true}
  184. }
  185. runtime.wg.Done()
  186. }
  187. func (runtime *rubyRuntime) detectRake(gemfileContent string, results chan struct {
  188. string
  189. bool
  190. },
  191. ) {
  192. rakeFound := false
  193. quotes := `["']`
  194. rakeRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %srake%s`, quotes, quotes))
  195. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  196. for scanner.Scan() {
  197. line := []byte(scanner.Text())
  198. if rakeRe.Match(line) {
  199. rakeFound = true
  200. break
  201. }
  202. }
  203. if rakeFound {
  204. results <- struct {
  205. string
  206. bool
  207. }{rake, true}
  208. }
  209. runtime.wg.Done()
  210. }
  211. func (runtime *rubyRuntime) DetectGithub(
  212. client *github.Client,
  213. directoryContent []*github.RepositoryContent,
  214. owner, name, path string,
  215. repoContentOptions github.RepositoryContentGetOptions,
  216. paketo, heroku *BuilderInfo,
  217. ) error {
  218. gemfileFound := false
  219. gemfileLockFound := false
  220. configRuFound := false
  221. rakefileFound := false
  222. for i := range directoryContent {
  223. name := directoryContent[i].GetName()
  224. if name == "Gemfile" {
  225. gemfileFound = true
  226. } else if name == "Gemfile.lock" {
  227. gemfileLockFound = true
  228. } else if name == "config.ru" {
  229. configRuFound = true
  230. } else if name == "Rakefile" || name == "Rakefile.rb" || name == "rakefile" || name == "rakefile.rb" {
  231. rakefileFound = true
  232. }
  233. }
  234. paketoBuildpackInfo := BuildpackInfo{
  235. Name: "Ruby",
  236. Buildpack: "gcr.io/paketo-buildpacks/ruby",
  237. }
  238. herokuBuildpackInfo := BuildpackInfo{
  239. Name: "Ruby",
  240. Buildpack: "heroku/ruby",
  241. }
  242. if !gemfileFound {
  243. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  244. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  245. return nil
  246. }
  247. fileContent, _, _, err := client.Repositories.GetContents(context.Background(), owner, name, "Gemfile", &repoContentOptions)
  248. if err != nil {
  249. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  250. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  251. return fmt.Errorf("error fetching contents of Gemfile for %s/%s: %v", owner, name, err)
  252. }
  253. gemfileContent, err := fileContent.GetContent()
  254. if err != nil {
  255. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  256. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  257. return fmt.Errorf("error calling GetContent() on Gemfile for %s/%s: %v", owner, name, err)
  258. }
  259. count := 6
  260. if !configRuFound {
  261. // unicorn needs config.ru
  262. count -= 1
  263. if !gemfileLockFound {
  264. // rackup needs one of Gemfile.lock or config.ru
  265. count -= 1
  266. }
  267. }
  268. if !rakefileFound {
  269. count -= 1
  270. }
  271. results := make(chan struct {
  272. string
  273. bool
  274. }, count)
  275. runtime.wg.Add(count)
  276. go runtime.detectPuma(gemfileContent, results)
  277. go runtime.detectThin(gemfileContent, results)
  278. if configRuFound {
  279. {
  280. // FIXME: find a better, more readable way of doing this
  281. results <- struct {
  282. string
  283. bool
  284. }{rackup, true}
  285. runtime.wg.Done()
  286. }
  287. go runtime.detectUnicorn(gemfileContent, results)
  288. }
  289. go runtime.detectPassenger(gemfileContent, results)
  290. if !configRuFound && gemfileLockFound {
  291. go runtime.detectRackupGithub(client, owner, name, repoContentOptions, results)
  292. }
  293. if rakefileFound {
  294. go runtime.detectRake(gemfileContent, results)
  295. }
  296. runtime.wg.Wait()
  297. close(results)
  298. paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
  299. heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
  300. return nil
  301. }
  302. func (runtime *rubyRuntime) DetectGitlab(
  303. client *gitlab.Client,
  304. tree []*gitlab.TreeNode,
  305. repoPath, path, ref string,
  306. paketo, heroku *BuilderInfo,
  307. ) error {
  308. gemfileFound := false
  309. gemfileLockFound := false
  310. configRuFound := false
  311. rakefileFound := false
  312. for i := range tree {
  313. name := tree[i].Name
  314. if name == "Gemfile" {
  315. gemfileFound = true
  316. } else if name == "Gemfile.lock" {
  317. gemfileLockFound = true
  318. } else if name == "config.ru" {
  319. configRuFound = true
  320. } else if name == "Rakefile" || name == "Rakefile.rb" || name == "rakefile" || name == "rakefile.rb" {
  321. rakefileFound = true
  322. }
  323. }
  324. paketoBuildpackInfo := BuildpackInfo{
  325. Name: "Ruby",
  326. Buildpack: "gcr.io/paketo-buildpacks/ruby",
  327. }
  328. herokuBuildpackInfo := BuildpackInfo{
  329. Name: "Ruby",
  330. Buildpack: "heroku/ruby",
  331. }
  332. if !gemfileFound {
  333. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  334. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  335. return nil
  336. }
  337. fileContent, _, err := client.RepositoryFiles.GetRawFile(
  338. repoPath, "Gemfile", &gitlab.GetRawFileOptions{
  339. Ref: gitlab.String(ref),
  340. })
  341. if err != nil {
  342. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  343. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  344. return fmt.Errorf("error fetching contents of Gemfile for %s: %v", repoPath, err)
  345. }
  346. gemfileContent := string(fileContent)
  347. count := 6
  348. if !configRuFound {
  349. // unicorn needs config.ru
  350. count -= 1
  351. if !gemfileLockFound {
  352. // rackup needs one of Gemfile.lock or config.ru
  353. count -= 1
  354. }
  355. }
  356. if !rakefileFound {
  357. count -= 1
  358. }
  359. results := make(chan struct {
  360. string
  361. bool
  362. }, count)
  363. runtime.wg.Add(count)
  364. go runtime.detectPuma(gemfileContent, results)
  365. go runtime.detectThin(gemfileContent, results)
  366. if configRuFound {
  367. {
  368. // FIXME: find a better, more readable way of doing this
  369. results <- struct {
  370. string
  371. bool
  372. }{rackup, true}
  373. runtime.wg.Done()
  374. }
  375. go runtime.detectUnicorn(gemfileContent, results)
  376. }
  377. go runtime.detectPassenger(gemfileContent, results)
  378. if !configRuFound && gemfileLockFound {
  379. go runtime.detectRackupGitlab(client, repoPath, ref, results)
  380. }
  381. if rakefileFound {
  382. go runtime.detectRake(gemfileContent, results)
  383. }
  384. runtime.wg.Wait()
  385. close(results)
  386. paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
  387. heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
  388. return nil
  389. }