api_python.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package buildpacks
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "strings"
  7. "sync"
  8. "github.com/google/go-github/github"
  9. "github.com/pelletier/go-toml"
  10. )
  11. type apiPythonRuntime struct {
  12. wg sync.WaitGroup
  13. packs map[string]*BuildpackInfo
  14. }
  15. // FIXME: should be called once at the top-level somewhere in the backend
  16. func populatePythonPacks(client *github.Client) map[string]*BuildpackInfo {
  17. packs := make(map[string]*BuildpackInfo)
  18. repoRelease, _, err := client.Repositories.GetLatestRelease(context.Background(), "paketo-buildpacks", "python")
  19. if err != nil {
  20. fmt.Printf("Error fetching latest release for paketo-buildpacks/python: %v\n", err)
  21. return nil
  22. }
  23. fileContent, _, _, err := client.Repositories.GetContents(
  24. context.Background(), "paketo-buildpacks", "python", "buildpack.toml",
  25. &github.RepositoryContentGetOptions{
  26. Ref: *repoRelease.TagName,
  27. },
  28. )
  29. if err != nil {
  30. fmt.Printf("Error fetching contents of buildpack.toml for paketo-buildpacks/python: %v\n", err)
  31. return nil
  32. }
  33. data, err := fileContent.GetContent()
  34. if err != nil {
  35. fmt.Printf("Error calling GetContent() on buildpack.toml for paketo-buildpacks/python: %v\n", err)
  36. return nil
  37. }
  38. buildpackToml, err := toml.Load(data)
  39. if err != nil {
  40. fmt.Printf("Error while reading buildpack.toml from paketo-buildpacks/python: %v\n", err)
  41. os.Exit(1)
  42. }
  43. order := buildpackToml.Get("order").([]*toml.Tree)
  44. // pipenv
  45. packs[pipenv] = newBuildpackInfo()
  46. pipenvGroup := order[0].GetArray("group").([]*toml.Tree)
  47. for i := 0; i < len(pipenvGroup); i++ {
  48. packs[pipenv].addPack(
  49. buildpackOrderGroupInfo{
  50. ID: pipenvGroup[i].Get("id").(string),
  51. Optional: pipenvGroup[i].GetDefault("optional", false).(bool),
  52. Version: pipenvGroup[i].Get("version").(string),
  53. },
  54. )
  55. }
  56. // pip
  57. packs[pip] = newBuildpackInfo()
  58. pipGroup := order[1].GetArray("group").([]*toml.Tree)
  59. for i := 0; i < len(pipGroup); i++ {
  60. packs[pip].addPack(
  61. buildpackOrderGroupInfo{
  62. ID: pipGroup[i].Get("id").(string),
  63. Optional: pipGroup[i].GetDefault("optional", false).(bool),
  64. Version: pipGroup[i].Get("version").(string),
  65. },
  66. )
  67. }
  68. // conda
  69. packs[conda] = newBuildpackInfo()
  70. condaGroup := order[2].GetArray("group").([]*toml.Tree)
  71. for i := 0; i < len(condaGroup); i++ {
  72. packs[pip].addPack(
  73. buildpackOrderGroupInfo{
  74. ID: condaGroup[i].Get("id").(string),
  75. Optional: condaGroup[i].GetDefault("optional", false).(bool),
  76. Version: condaGroup[i].Get("version").(string),
  77. },
  78. )
  79. }
  80. // no package manager
  81. packs[standalone] = newBuildpackInfo()
  82. standaloneGroup := order[3].GetArray("group").([]*toml.Tree)
  83. for i := 0; i < len(standaloneGroup); i++ {
  84. packs[standalone].addPack(
  85. buildpackOrderGroupInfo{
  86. ID: standaloneGroup[i].Get("id").(string),
  87. Optional: standaloneGroup[i].GetDefault("optional", false).(bool),
  88. Version: standaloneGroup[i].Get("version").(string),
  89. },
  90. )
  91. }
  92. return packs
  93. }
  94. func NewAPIPythonRuntime() APIRuntime {
  95. return &apiPythonRuntime{}
  96. }
  97. func (runtime *apiPythonRuntime) detectPipenv(results chan struct {
  98. string
  99. bool
  100. }, directoryContent []*github.RepositoryContent) {
  101. pipfileFound := false
  102. pipfileLockFound := false
  103. for i := 0; i < len(directoryContent); i++ {
  104. name := directoryContent[i].GetName()
  105. if name == "Pipfile" {
  106. pipfileFound = true
  107. } else if name == "Pipfile.lock" {
  108. pipfileLockFound = true
  109. }
  110. if pipfileFound && pipfileLockFound {
  111. break
  112. }
  113. }
  114. if pipfileFound && pipfileLockFound {
  115. results <- struct {
  116. string
  117. bool
  118. }{pipenv, true}
  119. } else {
  120. results <- struct {
  121. string
  122. bool
  123. }{pipenv, false}
  124. }
  125. runtime.wg.Done()
  126. }
  127. func (runtime *apiPythonRuntime) detectPip(results chan struct {
  128. string
  129. bool
  130. }, directoryContent []*github.RepositoryContent) {
  131. requirementsTxtFound := false
  132. for i := 0; i < len(directoryContent); i++ {
  133. name := directoryContent[i].GetName()
  134. if name == "requirements.txt" {
  135. requirementsTxtFound = true
  136. }
  137. }
  138. if requirementsTxtFound {
  139. results <- struct {
  140. string
  141. bool
  142. }{pip, true}
  143. } else {
  144. results <- struct {
  145. string
  146. bool
  147. }{pip, false}
  148. }
  149. runtime.wg.Done()
  150. }
  151. func (runtime *apiPythonRuntime) detectConda(results chan struct {
  152. string
  153. bool
  154. }, directoryContent []*github.RepositoryContent) {
  155. environmentFound := false
  156. packageListFound := false
  157. for i := 0; i < len(directoryContent); i++ {
  158. name := directoryContent[i].GetName()
  159. if name == "environment.yml" {
  160. environmentFound = true
  161. break
  162. } else if name == "package-list.txt" {
  163. packageListFound = true
  164. break
  165. }
  166. }
  167. if environmentFound || packageListFound {
  168. results <- struct {
  169. string
  170. bool
  171. }{conda, true}
  172. } else {
  173. results <- struct {
  174. string
  175. bool
  176. }{conda, false}
  177. }
  178. runtime.wg.Done()
  179. }
  180. func (runtime *apiPythonRuntime) detectStandalone(results chan struct {
  181. string
  182. bool
  183. }, directoryContent []*github.RepositoryContent) {
  184. pyFound := false
  185. for i := 0; i < len(directoryContent); i++ {
  186. name := directoryContent[i].GetName()
  187. if strings.HasSuffix(name, ".py") {
  188. pyFound = true
  189. break
  190. }
  191. }
  192. if pyFound {
  193. results <- struct {
  194. string
  195. bool
  196. }{standalone, true}
  197. } else {
  198. results <- struct {
  199. string
  200. bool
  201. }{standalone, false}
  202. }
  203. runtime.wg.Done()
  204. }
  205. func (runtime *apiPythonRuntime) Detect(
  206. client *github.Client,
  207. directoryContent []*github.RepositoryContent,
  208. owner, name, path string,
  209. repoContentOptions github.RepositoryContentGetOptions,
  210. ) *RuntimeResponse {
  211. runtime.packs = populatePythonPacks(client)
  212. results := make(chan struct {
  213. string
  214. bool
  215. }, 4)
  216. fmt.Printf("Starting detection for a Python runtime for %s/%s\n", owner, name)
  217. runtime.wg.Add(4)
  218. fmt.Println("Checking for pipenv")
  219. go runtime.detectPipenv(results, directoryContent)
  220. fmt.Println("Checking for pip")
  221. go runtime.detectPip(results, directoryContent)
  222. fmt.Println("Checking for conda")
  223. go runtime.detectConda(results, directoryContent)
  224. fmt.Println("Checking for Python standalone")
  225. go runtime.detectStandalone(results, directoryContent)
  226. runtime.wg.Wait()
  227. close(results)
  228. detected := make(map[string]bool)
  229. for result := range results {
  230. detected[result.string] = result.bool
  231. }
  232. // TODO: how to access config values for Python projects
  233. if detected[pipenv] {
  234. fmt.Printf("Python pipenv runtime detected for %s/%s\n", owner, name)
  235. return &RuntimeResponse{
  236. Name: "Python",
  237. Runtime: pipenv,
  238. Buildpacks: runtime.packs[pipenv],
  239. }
  240. } else if detected[pip] {
  241. fmt.Printf("Python pip runtime detected for %s/%s\n", owner, name)
  242. return &RuntimeResponse{
  243. Name: "Python",
  244. Runtime: pip,
  245. Buildpacks: runtime.packs[pip],
  246. }
  247. } else if detected[conda] {
  248. fmt.Printf("Python conda runtime detected for %s/%s\n", owner, name)
  249. return &RuntimeResponse{
  250. Name: "Python",
  251. Runtime: conda,
  252. Buildpacks: runtime.packs[conda],
  253. }
  254. } else if detected[standalone] {
  255. fmt.Printf("Python standalone runtime detected for %s/%s\n", owner, name)
  256. return &RuntimeResponse{
  257. Name: "Python",
  258. Runtime: standalone,
  259. Buildpacks: runtime.packs[standalone],
  260. }
  261. }
  262. fmt.Printf("No Python runtime detected for %s/%s\n", owner, name)
  263. return nil
  264. }