Class: Constancy::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/constancy/config.rb

Constant Summary collapse

CONFIG_FILENAMES =
%w( constancy.yml )
VALID_CONFIG_KEYS =
%w( sync consul vault constancy )
VALID_VAULT_KEY_PATTERNS =
[ %r{^vault\.[A-Za-z][A-Za-z0-9_-]*$}, %r{^vault$} ]
VALID_CONFIG_KEY_PATTERNS =
VALID_VAULT_KEY_PATTERNS
VALID_CONSUL_CONFIG_KEYS =
%w( url datacenter token_source )
VALID_VAULT_CONFIG_KEYS =
%w( url consul_token_path consul_token_field )
VALID_CONSTANCY_CONFIG_KEYS =
%w( verbose chomp delete color )
DEFAULT_CONSUL_URL =
"http://localhost:8500"
DEFAULT_CONSUL_TOKEN_SOURCE =
"none"
DEFAULT_VAULT_CONSUL_TOKEN_FIELD =
"token"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path: nil, targets: nil, call_external_apis: true) ⇒ Config

Returns a new instance of Config.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/constancy/config.rb', line 51

def initialize(path: nil, targets: nil, call_external_apis: true)
  if path.nil? or File.directory?(path)
    self.config_file = Constancy::Config.discover(dir: path)
  elsif File.exist?(path)
    self.config_file = path
  else
    raise Constancy::ConfigFileNotFound.new
  end

  if self.config_file.nil? or not File.exist?(self.config_file) or not File.readable?(self.config_file)
    raise Constancy::ConfigFileNotFound.new
  end

  self.config_file = File.expand_path(self.config_file)
  self.base_dir = File.dirname(self.config_file)
  self.target_allowlist = targets
  self.call_external_apis = call_external_apis
  parse!
end

Instance Attribute Details

#base_dirObject

Returns the value of attribute base_dir.



23
24
25
# File 'lib/constancy/config.rb', line 23

def base_dir
  @base_dir
end

#call_external_apisObject

Returns the value of attribute call_external_apis.



23
24
25
# File 'lib/constancy/config.rb', line 23

def call_external_apis
  @call_external_apis
end

#config_fileObject

Returns the value of attribute config_file.



23
24
25
# File 'lib/constancy/config.rb', line 23

def config_file
  @config_file
end

#consul_token_sourcesObject

Returns the value of attribute consul_token_sources.



23
24
25
# File 'lib/constancy/config.rb', line 23

def consul_token_sources
  @consul_token_sources
end

#consul_urlObject

Returns the value of attribute consul_url.



23
24
25
# File 'lib/constancy/config.rb', line 23

def consul_url
  @consul_url
end

#default_consul_token_sourceObject

Returns the value of attribute default_consul_token_source.



23
24
25
# File 'lib/constancy/config.rb', line 23

def default_consul_token_source
  @default_consul_token_source
end

#sync_targetsObject

Returns the value of attribute sync_targets.



23
24
25
# File 'lib/constancy/config.rb', line 23

def sync_targets
  @sync_targets
end

#target_allowlistObject

Returns the value of attribute target_allowlist.



23
24
25
# File 'lib/constancy/config.rb', line 23

def target_allowlist
  @target_allowlist
end

Class Method Details

.discover(dir: nil) ⇒ Object

discover the nearest config file



28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/constancy/config.rb', line 28

def discover(dir: nil)
  dir ||= Dir.pwd

  CONFIG_FILENAMES.each do |filename|
    full_path = File.join(dir, filename)
    if File.exist?(full_path)
      return full_path
    end
  end

  dir == "/" ? nil : self.discover(dir: File.dirname(dir))
end

.only_valid_config_keys!(keylist) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/constancy/config.rb', line 41

def only_valid_config_keys!(keylist)
  (keylist - VALID_CONFIG_KEYS).each do |key|
    if not VALID_CONFIG_KEY_PATTERNS.find { |pattern| key =~ pattern }
      raise Constancy::ConfigFileInvalid.new("'#{key}' is not a valid configuration key")
    end
  end
  true
end

Instance Method Details

#chomp?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/constancy/config.rb', line 75

def chomp?
  @do_chomp
end

#color?Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/constancy/config.rb', line 83

def color?
  @use_color
end

#delete?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/constancy/config.rb', line 79

def delete?
  @do_delete
end

#parse!Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/constancy/config.rb', line 93

def parse!
  raw = {}
  begin
    raw = YAML.load(ERB.new(File.read(self.config_file)).result)
  rescue
    raise Constancy::ConfigFileInvalid.new("Unable to parse config file as YAML")
  end

  if raw.is_a? FalseClass
    # this generally means an empty config file
    raw = {}
  end

  if not raw.is_a? Hash
    raise Constancy::ConfigFileInvalid.new("Config file must form a hash")
  end

  Constancy::Config.only_valid_config_keys!(raw.keys)

  self.consul_token_sources = {
    "none" => Constancy::PassiveTokenSource.new,
    "env" => Constancy::EnvTokenSource.new,
  }.merge(
    self.parse_vault_token_sources!(raw),
  )

  raw['consul'] ||= {}
  if not raw['consul'].is_a? Hash
    raise Constancy::ConfigFileInvalid.new("'consul' must be a hash")
  end

  if (raw['consul'].keys - VALID_CONSUL_CONFIG_KEYS) != []
    raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the consul config: #{VALID_CONSUL_CONFIG_KEYS.join(", ")}")
  end

  self.consul_url = raw['consul']['url'] || DEFAULT_CONSUL_URL
  srcname = raw['consul']['token_source'] || DEFAULT_CONSUL_TOKEN_SOURCE
  self.default_consul_token_source =
    self.consul_token_sources[srcname].tap do |src|
      if src.nil?
        raise Constancy::ConfigFileInvalid.new("Consul token source '#{consul_token_source}' is not defined")
      end
    end

  raw['constancy'] ||= {}
  if not raw['constancy'].is_a? Hash
    raise Constancy::ConfigFileInvalid.new("'constancy' must be a hash")
  end

  if (raw['constancy'].keys - VALID_CONSTANCY_CONFIG_KEYS) != []
    raise Constancy::ConfigFileInvalid.new("Only the following keys are valid in the 'constancy' config block: #{VALID_CONSTANCY_CONFIG_KEYS.join(", ")}")
  end

  # verbose: default false
  @is_verbose = raw['constancy']['verbose'] ? true : false
  if ENV['CONSTANCY_VERBOSE']
    @is_verbose = true
  end

  # chomp: default true
  if raw['constancy'].has_key?('chomp')
    @do_chomp = raw['constancy']['chomp'] ? true : false
  else
    @do_chomp = true
  end

  # delete: default false
  @do_delete = raw['constancy']['delete'] ? true : false

  raw['sync'] ||= []
  if not raw['sync'].is_a? Array
    raise Constancy::ConfigFileInvalid.new("'sync' must be an array")
  end

  # color: default true
  if raw['constancy'].has_key?('color')
    @use_color = raw['constancy']['color'] ? true : false
  else
    @use_color = true
  end

  self.sync_targets = []
  raw['sync'].each do |target|
    token_source = self.default_consul_token_source
    if target.is_a? Hash
      target['datacenter'] ||= raw['consul']['datacenter']
      if target['chomp'].nil?
        target['chomp'] = self.chomp?
      end
      if target['delete'].nil?
        target['delete'] = self.delete?
      end
      if not target['token_source'].nil?
        token_source = self.consul_token_sources[target['token_source']]
        if token_source.nil?
          raise Constancy::ConfigFileInvalid.new("Consul token source '#{target['token_source']}' is not defined")
        end
        target.delete('token_source')
      end
    end

    if not self.target_allowlist.nil?
      # unnamed targets cannot be allowlisted
      next if target['name'].nil?

      # named targets must be on the allowlist
      next if not self.target_allowlist.include?(target['name'])
    end

    # only try to fetch consul tokens if we are actually going to do work
    consul_token = if self.call_external_apis
                     token_source.consul_token
                   else
                     ""
                   end
    self.sync_targets << Constancy::SyncTarget.new(config: target, consul_url: consul_url, token_source: token_source, base_dir: self.base_dir, call_external_apis: self.call_external_apis)
  end
end

#parse_vault_token_sources!(raw) ⇒ Object



87
88
89
90
91
# File 'lib/constancy/config.rb', line 87

def parse_vault_token_sources!(raw)
  raw.keys.select { |key| VALID_VAULT_KEY_PATTERNS.find { |pattern| key =~ pattern } }.collect do |key|
    [key, Constancy::VaultTokenSource.new(name: key, config: raw[key])]
  end.to_h
end

#verbose?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/constancy/config.rb', line 71

def verbose?
  @is_verbose
end