blob: af10e0d1bc6e691a77805805cef651803d527ec5 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001// Copyright 2017 Google Inc. 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
15package compiler
16
17import (
18 "errors"
19 "fmt"
20 "io/ioutil"
21 "log"
22 "net/http"
23 "net/url"
24 "path/filepath"
25 "strings"
26
27 yaml "gopkg.in/yaml.v2"
28)
29
30var fileCache map[string][]byte
31var infoCache map[string]interface{}
32var count int64
33
34var verboseReader = false
35var fileCacheEnable = true
36var infoCacheEnable = true
37
38func initializeFileCache() {
39 if fileCache == nil {
40 fileCache = make(map[string][]byte, 0)
41 }
42}
43
44func initializeInfoCache() {
45 if infoCache == nil {
46 infoCache = make(map[string]interface{}, 0)
47 }
48}
49
50func DisableFileCache() {
51 fileCacheEnable = false
52}
53
54func DisableInfoCache() {
55 infoCacheEnable = false
56}
57
58func RemoveFromFileCache(fileurl string) {
59 if !fileCacheEnable {
60 return
61 }
62 initializeFileCache()
63 delete(fileCache, fileurl)
64}
65
66func RemoveFromInfoCache(filename string) {
67 if !infoCacheEnable {
68 return
69 }
70 initializeInfoCache()
71 delete(infoCache, filename)
72}
73
74// FetchFile gets a specified file from the local filesystem or a remote location.
75func FetchFile(fileurl string) ([]byte, error) {
76 var bytes []byte
77 initializeFileCache()
78 if fileCacheEnable {
79 bytes, ok := fileCache[fileurl]
80 if ok {
81 if verboseReader {
82 log.Printf("Cache hit %s", fileurl)
83 }
84 return bytes, nil
85 }
86 if verboseReader {
87 log.Printf("Fetching %s", fileurl)
88 }
89 }
90 response, err := http.Get(fileurl)
91 if err != nil {
92 return nil, err
93 }
94 defer response.Body.Close()
95 if response.StatusCode != 200 {
96 return nil, errors.New(fmt.Sprintf("Error downloading %s: %s", fileurl, response.Status))
97 }
98 bytes, err = ioutil.ReadAll(response.Body)
99 if fileCacheEnable && err == nil {
100 fileCache[fileurl] = bytes
101 }
102 return bytes, err
103}
104
105// ReadBytesForFile reads the bytes of a file.
106func ReadBytesForFile(filename string) ([]byte, error) {
107 // is the filename a url?
108 fileurl, _ := url.Parse(filename)
109 if fileurl.Scheme != "" {
110 // yes, fetch it
111 bytes, err := FetchFile(filename)
112 if err != nil {
113 return nil, err
114 }
115 return bytes, nil
116 }
117 // no, it's a local filename
118 bytes, err := ioutil.ReadFile(filename)
119 if err != nil {
120 return nil, err
121 }
122 return bytes, nil
123}
124
125// ReadInfoFromBytes unmarshals a file as a yaml.MapSlice.
126func ReadInfoFromBytes(filename string, bytes []byte) (interface{}, error) {
127 initializeInfoCache()
128 if infoCacheEnable {
129 cachedInfo, ok := infoCache[filename]
130 if ok {
131 if verboseReader {
132 log.Printf("Cache hit info for file %s", filename)
133 }
134 return cachedInfo, nil
135 }
136 if verboseReader {
137 log.Printf("Reading info for file %s", filename)
138 }
139 }
140 var info yaml.MapSlice
141 err := yaml.Unmarshal(bytes, &info)
142 if err != nil {
143 return nil, err
144 }
145 if infoCacheEnable && len(filename) > 0 {
146 infoCache[filename] = info
147 }
148 return info, nil
149}
150
151// ReadInfoForRef reads a file and return the fragment needed to resolve a $ref.
152func ReadInfoForRef(basefile string, ref string) (interface{}, error) {
153 initializeInfoCache()
154 if infoCacheEnable {
155 info, ok := infoCache[ref]
156 if ok {
157 if verboseReader {
158 log.Printf("Cache hit for ref %s#%s", basefile, ref)
159 }
160 return info, nil
161 }
162 if verboseReader {
163 log.Printf("Reading info for ref %s#%s", basefile, ref)
164 }
165 }
166 count = count + 1
167 basedir, _ := filepath.Split(basefile)
168 parts := strings.Split(ref, "#")
169 var filename string
170 if parts[0] != "" {
171 filename = basedir + parts[0]
172 } else {
173 filename = basefile
174 }
175 bytes, err := ReadBytesForFile(filename)
176 if err != nil {
177 return nil, err
178 }
179 info, err := ReadInfoFromBytes(filename, bytes)
180 if err != nil {
181 log.Printf("File error: %v\n", err)
182 } else {
183 if len(parts) > 1 {
184 path := strings.Split(parts[1], "/")
185 for i, key := range path {
186 if i > 0 {
187 m, ok := info.(yaml.MapSlice)
188 if ok {
189 found := false
190 for _, section := range m {
191 if section.Key == key {
192 info = section.Value
193 found = true
194 }
195 }
196 if !found {
197 infoCache[ref] = nil
198 return nil, NewError(nil, fmt.Sprintf("could not resolve %s", ref))
199 }
200 }
201 }
202 }
203 }
204 }
205 if infoCacheEnable {
206 infoCache[ref] = info
207 }
208 return info, nil
209}