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