ruby.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. package buildpacks
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "regexp"
  7. "strings"
  8. "sync"
  9. "github.com/google/go-github/github"
  10. )
  11. type rubyRuntime struct {
  12. wg sync.WaitGroup
  13. }
  14. func NewRubyRuntime() Runtime {
  15. return &rubyRuntime{}
  16. }
  17. func (runtime *rubyRuntime) detectPuma(gemfileContent string, results chan struct {
  18. string
  19. bool
  20. }) {
  21. pumaFound := false
  22. quotes := `["']`
  23. pumaRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %spuma%s`, quotes, quotes))
  24. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  25. for scanner.Scan() {
  26. line := []byte(scanner.Text())
  27. if pumaRe.Match(line) {
  28. pumaFound = true
  29. break
  30. }
  31. }
  32. if pumaFound {
  33. results <- struct {
  34. string
  35. bool
  36. }{puma, true}
  37. }
  38. runtime.wg.Done()
  39. }
  40. func (runtime *rubyRuntime) detectThin(gemfileContent string, results chan struct {
  41. string
  42. bool
  43. }) {
  44. thinFound := false
  45. quotes := `["']`
  46. thinRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %sthin%s`, quotes, quotes))
  47. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  48. for scanner.Scan() {
  49. line := []byte(scanner.Text())
  50. if thinRe.Match(line) {
  51. thinFound = true
  52. break
  53. }
  54. }
  55. if thinFound {
  56. results <- struct {
  57. string
  58. bool
  59. }{thin, true}
  60. }
  61. runtime.wg.Done()
  62. }
  63. func (runtime *rubyRuntime) detectUnicorn(gemfileContent string, results chan struct {
  64. string
  65. bool
  66. }) {
  67. unicornFound := false
  68. quotes := `["']`
  69. unicornRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %sunicorn%s`, quotes, quotes))
  70. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  71. for scanner.Scan() {
  72. line := []byte(scanner.Text())
  73. if unicornRe.Match(line) {
  74. unicornFound = true
  75. break
  76. }
  77. }
  78. if unicornFound {
  79. results <- struct {
  80. string
  81. bool
  82. }{unicorn, true}
  83. }
  84. runtime.wg.Done()
  85. }
  86. func (runtime *rubyRuntime) detectPassenger(gemfileContent string, results chan struct {
  87. string
  88. bool
  89. }) {
  90. passengerFound := false
  91. quotes := `["']`
  92. passengerRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %spassenger%s`, quotes, quotes))
  93. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  94. for scanner.Scan() {
  95. line := []byte(scanner.Text())
  96. if passengerRe.Match(line) {
  97. passengerFound = true
  98. break
  99. }
  100. }
  101. if passengerFound {
  102. results <- struct {
  103. string
  104. bool
  105. }{passenger, true}
  106. }
  107. runtime.wg.Done()
  108. }
  109. func (runtime *rubyRuntime) detectRackup(
  110. client *github.Client, owner, name string,
  111. repoContentOptions github.RepositoryContentGetOptions, results chan struct {
  112. string
  113. bool
  114. },
  115. ) {
  116. fileContent, _, _, err := client.Repositories.GetContents(context.Background(),
  117. owner, name, "Gemfile.lock", &repoContentOptions)
  118. if err != nil {
  119. runtime.wg.Done()
  120. return
  121. }
  122. gemfileLockContent, err := fileContent.GetContent()
  123. if err != nil {
  124. runtime.wg.Done()
  125. return
  126. }
  127. rackFound := false
  128. scanner := bufio.NewScanner(strings.NewReader(gemfileLockContent))
  129. for scanner.Scan() {
  130. if strings.TrimSpace(scanner.Text()) == "GEM" {
  131. for scanner.Scan() {
  132. if strings.Contains(scanner.Text(), "rack") {
  133. rackFound = true
  134. break
  135. }
  136. }
  137. }
  138. }
  139. if rackFound {
  140. results <- struct {
  141. string
  142. bool
  143. }{rackup, true}
  144. }
  145. runtime.wg.Done()
  146. }
  147. func (runtime *rubyRuntime) detectRake(gemfileContent string, results chan struct {
  148. string
  149. bool
  150. }) {
  151. rakeFound := false
  152. quotes := `["']`
  153. rakeRe := regexp.MustCompile(fmt.Sprintf(`^\s*gem %srake%s`, quotes, quotes))
  154. scanner := bufio.NewScanner(strings.NewReader(gemfileContent))
  155. for scanner.Scan() {
  156. line := []byte(scanner.Text())
  157. if rakeRe.Match(line) {
  158. rakeFound = true
  159. break
  160. }
  161. }
  162. if rakeFound {
  163. results <- struct {
  164. string
  165. bool
  166. }{rake, true}
  167. }
  168. runtime.wg.Done()
  169. }
  170. func (runtime *rubyRuntime) Detect(
  171. client *github.Client,
  172. directoryContent []*github.RepositoryContent,
  173. owner, name, path string,
  174. repoContentOptions github.RepositoryContentGetOptions,
  175. paketo, heroku *BuilderInfo,
  176. ) error {
  177. gemfileFound := false
  178. gemfileLockFound := false
  179. configRuFound := false
  180. rakefileFound := false
  181. for i := range directoryContent {
  182. name := directoryContent[i].GetName()
  183. if name == "Gemfile" {
  184. gemfileFound = true
  185. } else if name == "Gemfile.lock" {
  186. gemfileLockFound = true
  187. } else if name == "config.ru" {
  188. configRuFound = true
  189. } else if name == "Rakefile" || name == "Rakefile.rb" || name == "rakefile" || name == "rakefile.rb" {
  190. rakefileFound = true
  191. }
  192. }
  193. paketoBuildpackInfo := BuildpackInfo{
  194. Name: "Ruby",
  195. Buildpack: "paketobuildpacks/ruby",
  196. }
  197. herokuBuildpackInfo := BuildpackInfo{
  198. Name: "Ruby",
  199. Buildpack: "heroku/ruby",
  200. }
  201. if !gemfileFound {
  202. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  203. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  204. return nil
  205. }
  206. fileContent, _, _, err := client.Repositories.GetContents(context.Background(), owner, name, "Gemfile", &repoContentOptions)
  207. if err != nil {
  208. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  209. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  210. return fmt.Errorf("error fetching contents of Gemfile for %s/%s: %v", owner, name, err)
  211. }
  212. gemfileContent, err := fileContent.GetContent()
  213. if err != nil {
  214. paketo.Others = append(paketo.Others, paketoBuildpackInfo)
  215. heroku.Others = append(heroku.Others, herokuBuildpackInfo)
  216. return fmt.Errorf("error calling GetContent() on Gemfile for %s/%s: %v", owner, name, err)
  217. }
  218. count := 6
  219. if !configRuFound {
  220. // unicorn needs config.ru
  221. count -= 1
  222. if !gemfileLockFound {
  223. // rackup needs one of Gemfile.lock or config.ru
  224. count -= 1
  225. }
  226. }
  227. if !rakefileFound {
  228. count -= 1
  229. }
  230. results := make(chan struct {
  231. string
  232. bool
  233. }, count)
  234. runtime.wg.Add(count)
  235. go runtime.detectPuma(gemfileContent, results)
  236. go runtime.detectThin(gemfileContent, results)
  237. if configRuFound {
  238. {
  239. // FIXME: find a better, more readable way of doing this
  240. results <- struct {
  241. string
  242. bool
  243. }{rackup, true}
  244. runtime.wg.Done()
  245. }
  246. go runtime.detectUnicorn(gemfileContent, results)
  247. }
  248. go runtime.detectPassenger(gemfileContent, results)
  249. if !configRuFound && gemfileLockFound {
  250. go runtime.detectRackup(client, owner, name, repoContentOptions, results)
  251. }
  252. if rakefileFound {
  253. go runtime.detectRake(gemfileContent, results)
  254. }
  255. runtime.wg.Wait()
  256. close(results)
  257. paketo.Detected = append(paketo.Detected, paketoBuildpackInfo)
  258. heroku.Detected = append(heroku.Detected, herokuBuildpackInfo)
  259. return nil
  260. }