github.com/zmap/zlint@v1.1.0/integration/config.go (about) 1 // +build integration 2 3 package integration 4 5 import ( 6 "compress/bzip2" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "net/http" 14 "os" 15 "path" 16 "strings" 17 ) 18 19 // dataFile is a struct describing a named CSV data file that can be downloaded 20 // from a URL when it is not present already on disk. If the URL ends in "bz2" 21 // then the data at the given URL is assumed to be compressed with Bzip2 and 22 // will be automatically decompressed when fetching the URL to write the data 23 // file to disk. By default the first datafile in the set is assumed to have 24 // a header line that must be skipped for data processing. 25 type dataFile struct { 26 Name string 27 URL string 28 } 29 30 // Valid returns an error if the data file has an empty name or URL. 31 func (f dataFile) Valid() error { 32 if f.Name == "" { 33 return errors.New("Name is empty") 34 } 35 if f.URL == "" { 36 return errors.New("URL is empty") 37 } 38 return nil 39 } 40 41 // ExistsIn checks if a file matching the data file's name exists in the 42 // provided directory. 43 func (f dataFile) ExistsIn(dir string) (bool, error) { 44 p := path.Join(dir, f.Name) 45 if _, err := os.Stat(p); os.IsNotExist(err) { 46 return false, nil 47 } else if err != nil { 48 return false, err 49 } 50 return true, nil 51 } 52 53 // DownloadTo will fetch the data file from its URL and write the contents to 54 // a file in the provided directory, handling Gzip2 decompression if required. 55 // An error is returned if fetching the URL fails, or if the remote server 56 // returns a HTTP status code other than 200. 57 func (f dataFile) DownloadTo(dir string) error { 58 p := path.Join(dir, f.Name) 59 60 resp, err := http.Get(f.URL) 61 if err != nil { 62 return err 63 } 64 defer resp.Body.Close() 65 66 if expected := http.StatusOK; resp.StatusCode != expected { 67 return fmt.Errorf("bad HTTP response from %q: %d != %d\n", 68 f.URL, resp.StatusCode, expected) 69 } 70 71 var reader io.Reader = resp.Body 72 if strings.HasSuffix(f.URL, ".bz2") { 73 reader = bzip2.NewReader(reader) 74 } 75 76 dataBytes, err := ioutil.ReadAll(reader) 77 if err != nil { 78 return err 79 } 80 81 if err := ioutil.WriteFile(p, dataBytes, 0644); err != nil { 82 return err 83 } 84 85 return nil 86 } 87 88 // config is a struct holding integration test configuration data. 89 type config struct { 90 CacheDir string 91 Files []dataFile 92 Expected keyedCounts 93 } 94 95 // loadConfig returns a config struct populated from the JSON serialization in 96 // the given file or returns an error if reading or unmarshaling the config file 97 // fails. 98 func loadConfig(file string) (*config, error) { 99 jsonBytes, err := ioutil.ReadFile(file) 100 if err != nil { 101 return nil, err 102 } 103 104 var c config 105 if err := json.Unmarshal(jsonBytes, &c); err != nil { 106 return nil, err 107 } 108 109 return &c, nil 110 } 111 112 // Save persists a config in JSON form to the given file or returns an error. 113 func (c *config) Save(file string) error { 114 jsonBytes, err := json.MarshalIndent(c, "", " ") 115 if err != nil { 116 return err 117 } 118 119 return ioutil.WriteFile(file, jsonBytes, 0644) 120 } 121 122 // Valid returns an error if the config has an empty CacheDir, no Files, or if 123 // any of the Files are not valid data file configs. 124 func (c config) Valid() error { 125 if c.CacheDir == "" { 126 return errors.New("no CacheDir defined") 127 } 128 if len(c.Files) == 0 { 129 return errors.New("No Files defined") 130 } 131 for i, file := range c.Files { 132 if err := file.Valid(); err != nil { 133 return fmt.Errorf("File %d was not valid: %v\n", i, err) 134 } 135 } 136 return nil 137 } 138 139 // PrepareCache creates the CacheDir if it does not exist and will download any 140 // of the Files that are not present in the CacheDir. If force is true then data 141 // files will be downloaded even if they are already present in the cachedir. 142 // This can be used to force an update when the upstream file content has 143 // changed and a stale copy exists in the cache 144 func (c config) PrepareCache(force bool) error { 145 if _, err := os.Stat(c.CacheDir); os.IsNotExist(err) { 146 log.Printf("Creating cache directory %q\n", c.CacheDir) 147 os.Mkdir(c.CacheDir, 0744) 148 } else { 149 log.Printf("Using existing cache directory %q\n", c.CacheDir) 150 } 151 for i, f := range c.Files { 152 if exists, err := f.ExistsIn(c.CacheDir); err != nil { 153 log.Fatalf("error checking cache: %v\n", err) 154 } else if !exists || force { 155 log.Printf("Downloading data file %q (%d of %d, url: %q)", 156 f.Name, i+1, len(c.Files), f.URL) 157 if err := f.DownloadTo(c.CacheDir); err != nil { 158 log.Fatalf("Failed to download: %v", err) 159 } 160 log.Printf("Download complete") 161 } else { 162 log.Printf("Using cached data file %q", f.Name) 163 } 164 } 165 return nil 166 }