github.com/dnephin/dobi@v0.15.0/config/config.go (about)

     1  package config
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path/filepath"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/dnephin/configtf"
    11  	pth "github.com/dnephin/configtf/path"
    12  	"github.com/dnephin/dobi/logging"
    13  	"github.com/dnephin/dobi/tasks/task"
    14  	log "github.com/sirupsen/logrus"
    15  )
    16  
    17  // Config is a data object for a full config file
    18  type Config struct {
    19  	FilePath   string
    20  	Meta       *MetaConfig
    21  	Resources  map[string]Resource
    22  	WorkingDir string
    23  }
    24  
    25  // NewConfig returns a new Config object
    26  func NewConfig() *Config {
    27  	return &Config{
    28  		Resources: make(map[string]Resource),
    29  		Meta:      &MetaConfig{},
    30  	}
    31  }
    32  
    33  func (c *Config) add(name string, resource Resource) error {
    34  	if c.contains(name) {
    35  		return fmt.Errorf("duplicate resource name %q", name)
    36  	}
    37  	c.Resources[name] = resource
    38  	return nil
    39  }
    40  
    41  func (c *Config) contains(name string) bool {
    42  	_, exists := c.Resources[name]
    43  	return exists
    44  }
    45  
    46  // Sorted returns the list of resource names in alphabetical sort order
    47  func (c *Config) Sorted() []string {
    48  	names := []string{}
    49  	for name := range c.Resources {
    50  		names = append(names, name)
    51  	}
    52  	sort.Strings(names)
    53  	return names
    54  }
    55  
    56  // Load a configuration from a filename
    57  func Load(filename string) (*Config, error) {
    58  	fmtError := func(err error) error {
    59  		return fmt.Errorf("failed to load config from %q: %s", filename, err)
    60  	}
    61  
    62  	config, err := loadConfig(filename)
    63  	if err != nil {
    64  		return nil, fmtError(err)
    65  	}
    66  
    67  	absPath, err := filepath.Abs(filename)
    68  	if err != nil {
    69  		return nil, fmtError(err)
    70  	}
    71  	config.WorkingDir = filepath.Dir(absPath)
    72  	config.FilePath = absPath
    73  
    74  	if err = validate(config); err != nil {
    75  		return nil, fmtError(err)
    76  	}
    77  	return config, nil
    78  }
    79  
    80  func loadConfig(filename string) (*Config, error) {
    81  	data, err := ioutil.ReadFile(filename)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	config, err := LoadFromBytes(data)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	logging.Log.WithFields(log.Fields{"filename": filename}).Debug("Configuration loaded")
    90  	return config, nil
    91  }
    92  
    93  // validate validates all the resources in the config
    94  func validate(config *Config) error {
    95  	for name, resource := range config.Resources {
    96  		path := pth.NewPath(name)
    97  
    98  		if err := configtf.ValidateFields(path, resource); err != nil {
    99  			return err
   100  		}
   101  		if err := validateResourcesExist(path, config, resource.Dependencies()); err != nil {
   102  			return err
   103  		}
   104  		if err := resource.Validate(path, config); err != nil {
   105  			return err
   106  		}
   107  	}
   108  	return config.Meta.Validate(config)
   109  }
   110  
   111  // validateResourcesExist checks that the list of resources is defined in the
   112  // config and returns an error if a resources is not defined.
   113  func validateResourcesExist(path pth.Path, c *Config, names []string) error {
   114  	missing := []string{}
   115  	for _, name := range names {
   116  		resource := task.ParseName(name).Resource()
   117  		if _, ok := c.Resources[resource]; !ok {
   118  			missing = append(missing, resource)
   119  		}
   120  	}
   121  	if len(missing) != 0 {
   122  		return pth.Errorf(path, "missing dependencies: %s", strings.Join(missing, ", "))
   123  	}
   124  	return nil
   125  }