From 31b4d6c59fb0ad755f2d52e382063eb0b1fca735 Mon Sep 17 00:00:00 2001 From: elijah Date: Fri, 8 Apr 2016 12:39:40 -0700 Subject: environments: clean up the json inheritence system with a proper environment class, and fix bugs with partials and inheritance. requires latest leap_platform. --- lib/leap_cli/config/manager.rb | 284 ++++++++++++----------------------------- 1 file changed, 83 insertions(+), 201 deletions(-) (limited to 'lib/leap_cli/config/manager.rb') diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb index ca739c3..4ad8b71 100644 --- a/lib/leap_cli/config/manager.rb +++ b/lib/leap_cli/config/manager.rb @@ -9,10 +9,6 @@ end module LeapCli module Config - class Environment - attr_accessor :services, :tags, :provider - end - # # A class to manage all the objects in all the configuration files. # @@ -27,9 +23,6 @@ module LeapCli ## ATTRIBUTES ## - attr_reader :nodes, :common, :secrets - attr_reader :base_services, :base_tags, :base_provider, :base_common - # # returns the Hash of the contents of facts.json # @@ -51,7 +44,7 @@ module LeapCli # def environment_names @environment_names ||= begin - [nil] + (env.tags.field('environment') + nodes.field('environment')).compact.uniq + [nil] + (env.tags.field('environment') + env.nodes.field('environment')).compact.uniq end end @@ -59,26 +52,25 @@ module LeapCli # Returns the appropriate environment variable # def env(env=nil) - env ||= 'default' - e = @environments[env] ||= Environment.new - yield e if block_given? - e + @environments[env || 'default'] end # - # The default accessors for services, tags, and provider. + # The default accessors + # # For these defaults, use 'default' environment, or whatever # environment is pinned. # - def services - env(default_environment).services - end - def tags - env(default_environment).tags - end - def provider - env(default_environment).provider - end + # I think it might be an error that these are ever used + # and I would like to get rid of them. + # + def services; env(default_environment).services; end + def tags; env(default_environment).tags; end + def partials; env(default_environment).partials; end + def provider; env(default_environment).provider; end + def common; env(default_environment).common; end + def secrets; env(default_environment).secrets; end + def nodes; env(default_environment).nodes; end def default_environment LeapCli.leapfile.environment @@ -88,6 +80,21 @@ module LeapCli ## IMPORT EXPORT ## + def add_environment(args) + if args[:inherit] + parent = @environments[args.delete(:inherit)] + else + parent = nil + end + @environments[args[:name]] = Environment.new( + self, + args.delete(:name), + args.delete(:dir), + parent, + args + ) + end + # # load .json configuration files # @@ -95,69 +102,37 @@ module LeapCli @provider_dir = Path.provider # load base - @base_services = load_all_json(Path.named_path([:service_config, '*'], Path.provider_base), Config::Tag) - @base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag) - @base_common = load_json( Path.named_path(:common_config, Path.provider_base), Config::Object) - @base_provider = load_json( Path.named_path(:provider_config, Path.provider_base), Config::Provider) + add_environment(name: '_base_', dir: Path.provider_base) # load provider - @nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node) - @common = load_json( Path.named_path(:common_config, @provider_dir), Config::Object) - @secrets = load_json( Path.named_path(:secrets_config, @provider_dir), Config::Secrets) - @common.inherit_from! @base_common - - # For the default environment, load provider services, tags, and provider.json - log 3, :loading, 'default environment...' - env('default') do |e| - e.services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag, :no_dots => true) - e.tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag, :no_dots => true) - e.provider = load_json( Path.named_path(:provider_config, @provider_dir), Config::Provider, :assert => true) - e.provider.set_env('default') - e.services.inherit_from! @base_services - e.tags.inherit_from! @base_tags - e.provider.inherit_from! @base_provider - validate_provider(e.provider) - end + Util::assert_files_exist!(Path.named_path(:provider_config, @provider_dir)) + add_environment(name: 'default', dir: @provider_dir, + inherit: '_base_', no_dots: true) - # create a special '_all_' environment, used for tracking the union - # of all the environments - env('_all_') do |e| - e.services = Config::ObjectList.new - e.tags = Config::ObjectList.new - e.provider = Config::Provider.new - e.services.inherit_from! env('default').services - e.tags.inherit_from! env('default').tags - e.provider.inherit_from! env('default').provider - e.provider.set_env('_all_') - end + # create a special '_all_' environment, used for tracking + # the union of all the environments + add_environment(name: '_all_', inherit: 'default') - # For each defined environment, load provider services, tags, and provider.json. + # load environments environment_names.each do |ename| - next unless ename - log 3, :loading, '%s environment...' % ename - env(ename) do |e| - e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename) - e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag, :env => ename) - e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider, :env => ename) - e.services.inherit_from! env('default').services - e.tags.inherit_from! env('default').tags - e.provider.inherit_from! env('default').provider - e.provider.set_env(ename) - validate_provider(e.provider) + if ename + log 3, :loading, '%s environment...' % ename + add_environment(name: ename, dir: @provider_dir, + inherit: 'default', scope: ename) end end # apply inheritance - @nodes.each do |name, node| + env.nodes.each do |name, node| Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'" - @nodes[name] = apply_inheritance(node) + env.nodes[name] = apply_inheritance(node) end # do some node-list post-processing cleanup_node_lists(options) # apply control files - @nodes.each do |name, node| + env.nodes.each do |name, node| control_files(node).each do |file| begin node.eval_file file @@ -185,7 +160,7 @@ module LeapCli existing_files = nil unless node_list - node_list = self.nodes + node_list = env.nodes existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir)) existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir)) end @@ -220,8 +195,8 @@ module LeapCli end def export_secrets(clean_unused_secrets = false) - if @secrets.any? - Util.write_file!([:secrets_config, @provider_dir], @secrets.dump_json(clean_unused_secrets) + "\n") + if env.secrets.any? + Util.write_file!([:secrets_config, @provider_dir], env.secrets.dump_json(clean_unused_secrets) + "\n") end end @@ -266,7 +241,7 @@ module LeapCli # so, take the part before the first period as the node name name = name.split('.').first end - @nodes[name] + env.nodes[name] end # @@ -280,11 +255,11 @@ module LeapCli # yields each node, in sorted order # def each_node(&block) - nodes.each_node &block + env.nodes.each_node &block end def reload_node!(node) - @nodes[node.name] = apply_inheritance!(node) + env.nodes[node.name] = apply_inheritance!(node) end ## @@ -305,32 +280,6 @@ module LeapCli @connections ||= ConnectionList.new end - ## - ## PARTIALS - ## - - # - # returns all the partial data for the specified partial path. - # partial path is always relative to provider root, but there must be multiple files - # that match because provider root might be the base provider or the local provider. - # - def partials(partial_path) - @partials ||= {} - if @partials[partial_path].nil? - [Path.provider_base, Path.provider].each do |provider_dir| - path = File.join(provider_dir, partial_path) - if File.exists?(path) - @partials[partial_path] ||= [] - @partials[partial_path] << load_json(path, Config::Object) - end - end - if @partials[partial_path].nil? - raise RuntimeError, 'no such partial path `%s`' % partial_path, caller - end - end - @partials[partial_path] - end - # # Loads a json template file as a Hash (used only when creating a new node .json # file for the first time). @@ -344,108 +293,23 @@ module LeapCli end end - private - - def load_all_json(pattern, object_class, options={}) - results = Config::ObjectList.new - Dir.glob(pattern).each do |filename| - next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/ - obj = load_json(filename, object_class) - if obj - name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1') - obj['name'] ||= name - if options[:env] - obj.environment = options[:env] - end - results[name] = obj - end - end - results - end - - def load_json(filename, object_class, options={}) - if options[:assert] - Util::assert_files_exist!(filename) - end - if !File.exists?(filename) - return object_class.new(self) - end - - log :loading, filename, 3 - - # - # Read a JSON file, strip out comments. - # - # UTF8 is the default encoding for JSON, but others are allowed: - # https://www.ietf.org/rfc/rfc4627.txt - # - buffer = StringIO.new - File.open(filename, "rb", :encoding => 'UTF-8') do |f| - while (line = f.gets) - next if line =~ /^\s*\/\// - buffer << line - end - end - - # - # force UTF-8 - # - if $ruby_version >= [1,9] - string = buffer.string.force_encoding('utf-8') - else - string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string) - end - - # parse json - begin - hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {} - rescue SyntaxError, JSON::ParserError => exc - log 0, :error, 'in file "%s":' % filename - log 0, exc.to_s, :indent => 1 - return nil - end - object = object_class.new(self) - object.deep_merge!(hash) - return object - end + ## + ## PRIVATE + ## - # - # remove all the nesting from a hash. - # - # def flatten_hash(input = {}, output = {}, options = {}) - # input.each do |key, value| - # key = options[:prefix].nil? ? "#{key}" : "#{options[:prefix]}#{options[:delimiter]||"_"}#{key}" - # if value.is_a? Hash - # flatten_hash(value, output, :prefix => key, :delimiter => options[:delimiter]) - # else - # output[key] = value - # end - # end - # output.replace(input) - # output - # end + private # # makes a node inherit options from appropriate the common, service, and tag json files. # def apply_inheritance(node, throw_exceptions=false) - new_node = Config::Node.new(self) - name = node.name - - # Guess the environment of the node from the tag names. - # (Technically, this is wrong: a tag that sets the environment might not be - # named the same as the environment. This code assumes that it is). - node_env = self.env - if node['tags'] - node['tags'].to_a.each do |tag| - if self.environment_names.include?(tag) - node_env = self.env(tag) - end - end - end + new_node = Config::Node.new(nil) + name = node.name + node_env = guess_node_env(node) + new_node.set_environment(node_env, new_node) # inherit from common - new_node.deep_merge!(@common) + new_node.deep_merge!(node_env.common) # inherit from services if node['services'] @@ -487,6 +351,28 @@ module LeapCli apply_inheritance(node, true) end + # + # Guess the environment of the node from the tag names. + # + # Technically, this is wrong: a tag that sets the environment might not be + # named the same as the environment. This code assumes that it is. + # + # Unfortunately, it is a chicken and egg problem. We need to know the nodes + # likely environment in order to apply the inheritance that will actually + # determine the node's properties. + # + def guess_node_env(node) + environment = self.env(default_environment) + if node['tags'] + node['tags'].to_a.each do |tag| + if self.environment_names.include?(tag) + environment = self.env(tag) + end + end + end + return environment + end + # # does some final clean at the end of loading nodes. # this includes removing disabled nodes, and populating @@ -494,7 +380,7 @@ module LeapCli # def cleanup_node_lists(options) @disabled_nodes = Config::ObjectList.new - @nodes.each do |name, node| + env.nodes.each do |name, node| if node.enabled || options[:include_disabled] if node['services'] node['services'].to_a.each do |node_service| @@ -510,16 +396,12 @@ module LeapCli end elsif !options[:include_disabled] log 2, :skipping, "disabled node #{name}." - @nodes.delete(name) + env.nodes.delete(name) @disabled_nodes[name] = node end end end - def validate_provider(provider) - # nothing yet. - end - # # returns a list of 'control' files for this node. # a control file is like a service or a tag JSON file, but it contains -- cgit v1.2.3