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  }