diff --git a/lib/bolt/inventory.rb b/lib/bolt/inventory.rb index 6c0cdad342..d52905955e 100644 --- a/lib/bolt/inventory.rb +++ b/lib/bolt/inventory.rb @@ -55,7 +55,8 @@ def self.schema schema = { type: Hash, properties: OPTIONS.map { |opt| [opt, _ref: opt] }.to_h, - definitions: DEFINITIONS + definitions: DEFINITIONS, + _plugin: true } schema[:definitions]['config'][:properties] = Bolt::Config.transport_definitions diff --git a/lib/bolt/inventory/group.rb b/lib/bolt/inventory/group.rb index df814f2392..35df0479fe 100644 --- a/lib/bolt/inventory/group.rb +++ b/lib/bolt/inventory/group.rb @@ -18,12 +18,20 @@ class Group GROUP_KEYS = DATA_KEYS + %w[name groups targets] CONFIG_KEYS = Bolt::Config::INVENTORY_OPTIONS.keys - def initialize(input, plugins) + def initialize(input, plugins, all_group: false) @logger = Bolt::Logger.logger(self) @plugins = plugins input = @plugins.resolve_top_level_references(input) if @plugins.reference?(input) + if all_group + if input.key?('name') && input['name'] != 'all' + @logger.warn("Top-level group '#{input['name']}' cannot specify a name, using 'all' instead.") + end + + input = input.merge('name' => 'all') + end + raise ValidationError.new("Group does not have a name", nil) unless input.key?('name') @name = @plugins.resolve_references(input['name']) diff --git a/lib/bolt/inventory/inventory.rb b/lib/bolt/inventory/inventory.rb index 5a1cdfa53d..9d7d7b8f3a 100644 --- a/lib/bolt/inventory/inventory.rb +++ b/lib/bolt/inventory/inventory.rb @@ -21,7 +21,7 @@ def initialize(data, transport, transports, plugins) @transport = transport @config = transports @plugins = plugins - @groups = Group.new(@data.merge('name' => 'all'), plugins) + @groups = Group.new(@data, plugins, all_group: true) @group_lookup = {} @targets = {} diff --git a/rakelib/schemas.rake b/rakelib/schemas.rake index facde42491..ff8598497b 100644 --- a/rakelib/schemas.rake +++ b/rakelib/schemas.rake @@ -211,7 +211,7 @@ end def add_plugin_reference(definition) definition, data = definition.partition do |k, _| - k == :description + %i[definitions description].include?(k) end.map(&:to_h) definition[:oneOf] = [ diff --git a/schemas/bolt-inventory.schema.json b/schemas/bolt-inventory.schema.json index ccb67351a1..66e859e3df 100644 --- a/schemas/bolt-inventory.schema.json +++ b/schemas/bolt-inventory.schema.json @@ -1353,25 +1353,74 @@ ] } }, - "type": "object", - "properties": { - "config": { - "$ref": "#/definitions/config" - }, - "facts": { - "$ref": "#/definitions/facts" - }, - "features": { - "$ref": "#/definitions/features" - }, - "groups": { - "$ref": "#/definitions/groups" - }, - "targets": { - "$ref": "#/definitions/targets" + "oneOf": [ + { + "type": "object", + "properties": { + "config": { + "oneOf": [ + { + "$ref": "#/definitions/config" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + }, + "facts": { + "oneOf": [ + { + "$ref": "#/definitions/facts" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + }, + "features": { + "oneOf": [ + { + "$ref": "#/definitions/features" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + }, + "groups": { + "oneOf": [ + { + "$ref": "#/definitions/groups" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + }, + "targets": { + "oneOf": [ + { + "$ref": "#/definitions/targets" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + }, + "vars": { + "oneOf": [ + { + "$ref": "#/definitions/vars" + }, + { + "$ref": "#/definitions/_plugin" + } + ] + } + } }, - "vars": { - "$ref": "#/definitions/vars" + { + "$ref": "#/definitions/_plugin" } - } + ] } \ No newline at end of file diff --git a/spec/bolt/inventory/inventory_spec.rb b/spec/bolt/inventory/inventory_spec.rb index fad29a5e92..cf355ae989 100644 --- a/spec/bolt/inventory/inventory_spec.rb +++ b/spec/bolt/inventory/inventory_spec.rb @@ -366,7 +366,6 @@ def get_target(inventory, name, alia = nil) context 'with targets at the top level' do let(:data) { { - 'name' => 'group1', 'targets' => [ 'target1', { 'uri' => 'target2' }, @@ -791,7 +790,6 @@ def common_data(transport) context 'with targets at the top level' do let(:data) { { - 'name' => 'group1', 'targets' => [ 'target1', { 'uri' => 'target2' }, diff --git a/spec/bolt/transport/local_spec.rb b/spec/bolt/transport/local_spec.rb index 7276246b80..dfa96289d1 100644 --- a/spec/bolt/transport/local_spec.rb +++ b/spec/bolt/transport/local_spec.rb @@ -44,8 +44,7 @@ def get_target(inventory, name, alia = nil) context 'with group-level config' do let(:data) { - { 'name' => 'locomoco', - 'targets' => [uri], + { 'targets' => [uri], 'config' => { 'transport' => 'ssh', 'local' => { @@ -100,8 +99,7 @@ def get_target(inventory, name, alia = nil) context 'with group-level config' do let(:data) { - { 'name' => 'locomoco', - 'targets' => [uri], + { 'targets' => [uri], 'config' => { 'local' => { 'bundled-ruby' => true, diff --git a/spec/integration/inventory_spec.rb b/spec/integration/inventory_spec.rb index 987eeb33ba..7cc677e4ca 100644 --- a/spec/integration/inventory_spec.rb +++ b/spec/integration/inventory_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'bolt_spec/conn' +require 'bolt_spec/env_var' require 'bolt_spec/files' require 'bolt_spec/integration' require 'bolt_spec/project' @@ -9,6 +10,7 @@ describe 'running with an inventory file', reset_puppet_settings: true do include BoltSpec::Conn + include BoltSpec::EnvVar include BoltSpec::Files include BoltSpec::Integration include BoltSpec::Project @@ -692,4 +694,74 @@ def fact_plan(name = 'facts_test') expect(result['targets'].empty?).to be(true) end end + + context 'top-level plugin' do + let(:command) { %W[inventory show --targets all --project #{@project.path}] } + let(:env_var) { 'BOLT_INVENTORY_PARTIAL' } + let(:partial_path) { 'partial.yaml' } + + let(:inventory) do + { + '_plugin' => 'yaml', + 'filepath' => partial_path + } + end + + let(:partial) do + { + 'targets' => %w[foo bar baz] + } + end + + around(:each) do |example| + with_env_vars(env_var => partial_path) do + with_project(inventory: inventory) do |project| + @project = project + File.write(project.path + partial_path, partial.to_yaml) + example.run + end + end + end + + context 'with valid resolved data' do + it 'does not error' do + result = run_cli_json(command) + expect(result['targets']).to include(*partial['targets']) + end + end + + context 'with resolved data with a name' do + let(:partial) do + { + 'name' => 'badname', + 'targets' => %w[foo bar baz] + } + end + + it 'warns' do + run_cli_json(command) + + expect(@log_output.readlines).to include( + /WARN.*Top-level group 'badname' cannot specify a name, using 'all' instead/ + ) + end + end + + context 'with nested plugins' do + let(:inventory) do + { + '_plugin' => 'yaml', + 'filepath' => { + '_plugin' => 'env_var', + 'var' => env_var + } + } + end + + it 'resolves and does not error' do + result = run_cli_json(command) + expect(result['targets']).to include(*partial['targets']) + end + end + end end diff --git a/spec/lib/bolt_spec/env_var.rb b/spec/lib/bolt_spec/env_var.rb new file mode 100644 index 0000000000..7b60525456 --- /dev/null +++ b/spec/lib/bolt_spec/env_var.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module BoltSpec + module EnvVar + def with_env_vars(new_vars) + new_vars.transform_keys!(&:to_s) + + begin + old_vars = new_vars.keys.collect { |var| [var, ENV[var]] }.to_h + ENV.update(new_vars) + yield + ensure + ENV.update(old_vars) if old_vars + end + end + end +end