reader.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // Copyright 2017 Google LLC. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package compiler
  15. import (
  16. "fmt"
  17. "io/ioutil"
  18. "log"
  19. "net/http"
  20. "net/url"
  21. "path/filepath"
  22. "strings"
  23. "sync"
  24. yaml "gopkg.in/yaml.v3"
  25. )
  26. var verboseReader = false
  27. var fileCache map[string][]byte
  28. var infoCache map[string]*yaml.Node
  29. var fileCacheEnable = true
  30. var infoCacheEnable = true
  31. // These locks are used to synchronize accesses to the fileCache and infoCache
  32. // maps (above). They are global state and can throw thread-related errors
  33. // when modified from separate goroutines. The general strategy is to protect
  34. // all public functions in this file with mutex Lock() calls. As a result, to
  35. // avoid deadlock, these public functions should not call other public
  36. // functions, so some public functions have private equivalents.
  37. // In the future, we might consider replacing the maps with sync.Map and
  38. // eliminating these mutexes.
  39. var fileCacheMutex sync.Mutex
  40. var infoCacheMutex sync.Mutex
  41. func initializeFileCache() {
  42. if fileCache == nil {
  43. fileCache = make(map[string][]byte, 0)
  44. }
  45. }
  46. func initializeInfoCache() {
  47. if infoCache == nil {
  48. infoCache = make(map[string]*yaml.Node, 0)
  49. }
  50. }
  51. // EnableFileCache turns on file caching.
  52. func EnableFileCache() {
  53. fileCacheMutex.Lock()
  54. defer fileCacheMutex.Unlock()
  55. fileCacheEnable = true
  56. }
  57. // EnableInfoCache turns on parsed info caching.
  58. func EnableInfoCache() {
  59. infoCacheMutex.Lock()
  60. defer infoCacheMutex.Unlock()
  61. infoCacheEnable = true
  62. }
  63. // DisableFileCache turns off file caching.
  64. func DisableFileCache() {
  65. fileCacheMutex.Lock()
  66. defer fileCacheMutex.Unlock()
  67. fileCacheEnable = false
  68. }
  69. // DisableInfoCache turns off parsed info caching.
  70. func DisableInfoCache() {
  71. infoCacheMutex.Lock()
  72. defer infoCacheMutex.Unlock()
  73. infoCacheEnable = false
  74. }
  75. // RemoveFromFileCache removes an entry from the file cache.
  76. func RemoveFromFileCache(fileurl string) {
  77. fileCacheMutex.Lock()
  78. defer fileCacheMutex.Unlock()
  79. if !fileCacheEnable {
  80. return
  81. }
  82. initializeFileCache()
  83. delete(fileCache, fileurl)
  84. }
  85. // RemoveFromInfoCache removes an entry from the info cache.
  86. func RemoveFromInfoCache(filename string) {
  87. infoCacheMutex.Lock()
  88. defer infoCacheMutex.Unlock()
  89. if !infoCacheEnable {
  90. return
  91. }
  92. initializeInfoCache()
  93. delete(infoCache, filename)
  94. }
  95. // GetInfoCache returns the info cache map.
  96. func GetInfoCache() map[string]*yaml.Node {
  97. infoCacheMutex.Lock()
  98. defer infoCacheMutex.Unlock()
  99. if infoCache == nil {
  100. initializeInfoCache()
  101. }
  102. return infoCache
  103. }
  104. // ClearFileCache clears the file cache.
  105. func ClearFileCache() {
  106. fileCacheMutex.Lock()
  107. defer fileCacheMutex.Unlock()
  108. fileCache = make(map[string][]byte, 0)
  109. }
  110. // ClearInfoCache clears the info cache.
  111. func ClearInfoCache() {
  112. infoCacheMutex.Lock()
  113. defer infoCacheMutex.Unlock()
  114. infoCache = make(map[string]*yaml.Node)
  115. }
  116. // ClearCaches clears all caches.
  117. func ClearCaches() {
  118. ClearFileCache()
  119. ClearInfoCache()
  120. }
  121. // FetchFile gets a specified file from the local filesystem or a remote location.
  122. func FetchFile(fileurl string) ([]byte, error) {
  123. fileCacheMutex.Lock()
  124. defer fileCacheMutex.Unlock()
  125. return fetchFile(fileurl)
  126. }
  127. func fetchFile(fileurl string) ([]byte, error) {
  128. var bytes []byte
  129. initializeFileCache()
  130. if fileCacheEnable {
  131. bytes, ok := fileCache[fileurl]
  132. if ok {
  133. if verboseReader {
  134. log.Printf("Cache hit %s", fileurl)
  135. }
  136. return bytes, nil
  137. }
  138. if verboseReader {
  139. log.Printf("Fetching %s", fileurl)
  140. }
  141. }
  142. response, err := http.Get(fileurl)
  143. if err != nil {
  144. return nil, err
  145. }
  146. defer response.Body.Close()
  147. if response.StatusCode != 200 {
  148. return nil, fmt.Errorf("Error downloading %s: %s", fileurl, response.Status)
  149. }
  150. bytes, err = ioutil.ReadAll(response.Body)
  151. if fileCacheEnable && err == nil {
  152. fileCache[fileurl] = bytes
  153. }
  154. return bytes, err
  155. }
  156. // ReadBytesForFile reads the bytes of a file.
  157. func ReadBytesForFile(filename string) ([]byte, error) {
  158. fileCacheMutex.Lock()
  159. defer fileCacheMutex.Unlock()
  160. return readBytesForFile(filename)
  161. }
  162. func readBytesForFile(filename string) ([]byte, error) {
  163. // is the filename a url?
  164. fileurl, _ := url.Parse(filename)
  165. if fileurl.Scheme != "" {
  166. // yes, fetch it
  167. bytes, err := fetchFile(filename)
  168. if err != nil {
  169. return nil, err
  170. }
  171. return bytes, nil
  172. }
  173. // no, it's a local filename
  174. bytes, err := ioutil.ReadFile(filename)
  175. if err != nil {
  176. return nil, err
  177. }
  178. return bytes, nil
  179. }
  180. // ReadInfoFromBytes unmarshals a file as a *yaml.Node.
  181. func ReadInfoFromBytes(filename string, bytes []byte) (*yaml.Node, error) {
  182. infoCacheMutex.Lock()
  183. defer infoCacheMutex.Unlock()
  184. return readInfoFromBytes(filename, bytes)
  185. }
  186. func readInfoFromBytes(filename string, bytes []byte) (*yaml.Node, error) {
  187. initializeInfoCache()
  188. if infoCacheEnable {
  189. cachedInfo, ok := infoCache[filename]
  190. if ok {
  191. if verboseReader {
  192. log.Printf("Cache hit info for file %s", filename)
  193. }
  194. return cachedInfo, nil
  195. }
  196. if verboseReader {
  197. log.Printf("Reading info for file %s", filename)
  198. }
  199. }
  200. var info yaml.Node
  201. err := yaml.Unmarshal(bytes, &info)
  202. if err != nil {
  203. return nil, err
  204. }
  205. if infoCacheEnable && len(filename) > 0 {
  206. infoCache[filename] = &info
  207. }
  208. return &info, nil
  209. }
  210. // ReadInfoForRef reads a file and return the fragment needed to resolve a $ref.
  211. func ReadInfoForRef(basefile string, ref string) (*yaml.Node, error) {
  212. fileCacheMutex.Lock()
  213. defer fileCacheMutex.Unlock()
  214. infoCacheMutex.Lock()
  215. defer infoCacheMutex.Unlock()
  216. initializeInfoCache()
  217. if infoCacheEnable {
  218. info, ok := infoCache[ref]
  219. if ok {
  220. if verboseReader {
  221. log.Printf("Cache hit for ref %s#%s", basefile, ref)
  222. }
  223. return info, nil
  224. }
  225. if verboseReader {
  226. log.Printf("Reading info for ref %s#%s", basefile, ref)
  227. }
  228. }
  229. basedir, _ := filepath.Split(basefile)
  230. parts := strings.Split(ref, "#")
  231. var filename string
  232. if parts[0] != "" {
  233. filename = parts[0]
  234. if _, err := url.ParseRequestURI(parts[0]); err != nil {
  235. // It is not an URL, so the file is local
  236. filename = basedir + parts[0]
  237. }
  238. } else {
  239. filename = basefile
  240. }
  241. bytes, err := readBytesForFile(filename)
  242. if err != nil {
  243. return nil, err
  244. }
  245. info, err := readInfoFromBytes(filename, bytes)
  246. if info != nil && info.Kind == yaml.DocumentNode {
  247. info = info.Content[0]
  248. }
  249. if err != nil {
  250. log.Printf("File error: %v\n", err)
  251. } else {
  252. if info == nil {
  253. return nil, NewError(nil, fmt.Sprintf("could not resolve %s", ref))
  254. }
  255. if len(parts) > 1 {
  256. path := strings.Split(parts[1], "/")
  257. for i, key := range path {
  258. if i > 0 {
  259. m := info
  260. if true {
  261. found := false
  262. for i := 0; i < len(m.Content); i += 2 {
  263. if m.Content[i].Value == key {
  264. info = m.Content[i+1]
  265. found = true
  266. }
  267. }
  268. if !found {
  269. infoCache[ref] = nil
  270. return nil, NewError(nil, fmt.Sprintf("could not resolve %s", ref))
  271. }
  272. }
  273. }
  274. }
  275. }
  276. }
  277. if infoCacheEnable {
  278. infoCache[ref] = info
  279. }
  280. return info, nil
  281. }