Class: Bolt::PAL

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/pal.rb

Constant Summary collapse

BOLTLIB_PATH =
File.join(__FILE__, '../../../bolt-modules')

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ PAL

Returns a new instance of PAL.



8
9
10
11
12
13
14
15
# File 'lib/bolt/pal.rb', line 8

def initialize(config)
  # Nothing works without initialized this global state. Reinitializing
  # is safe and in practice only happen in tests
  self.class.load_puppet
  self.class.configure_logging(config[:log_level])

  @config = config
end

Class Method Details

.configure_logging(log_level) ⇒ Object

Puppet logging is global so this is class method to avoid confusion



18
19
20
21
# File 'lib/bolt/pal.rb', line 18

def self.configure_logging(log_level)
  Puppet[:log_level] = log_level == :debug ? 'debug' : 'notice'
  Puppet::Util::Log.newdestination(:console)
end

.load_puppetObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/bolt/pal.rb', line 23

def self.load_puppet
  if Gem.win_platform?
    # Windows 'fix' for openssl behaving strangely. Prevents very slow operation
    # of random_bytes later when establishing winrm connections from a Windows host.
    # See https://github.com/rails/rails/issues/25805 for background.
    require 'openssl'
    OpenSSL::Random.random_bytes(1)
  end

  begin
    require_relative '../../vendored/require_vendored'
  rescue LoadError
    raise Bolt::CLIError, "Puppet must be installed to execute tasks"
  end

  # Now that puppet is loaded we can include puppet mixins in data types
  Bolt::ResultSet.include_iterable

  # TODO: This is a hack for PUP-8441 remove it once that is fixed
  require_relative '../../vendored/puppet/lib/puppet/datatypes/impl/error.rb'
  Puppet::DataTypes::Error.class_eval do
    def to_json(opts = nil)
      _pcore_init_hash.to_json(opts)
    end
  end
end

Instance Method Details

#add_target_spec(compiler) ⇒ Object

Create a top-level alias for TargetSpec so that users don’t have to namespace it with Boltlib, which is just an implementation detail. This allows TargetSpec to feel like a built-in type in bolt, rather than something has been, no pun intended, “bolted on”.



54
55
56
# File 'lib/bolt/pal.rb', line 54

def add_target_spec(compiler)
  compiler.evaluate_string('type TargetSpec = Boltlib::TargetSpec')
end

#get_plan_info(plan_name) ⇒ Object



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
# File 'lib/bolt/pal.rb', line 159

def get_plan_info(plan_name)
  plan = in_bolt_compiler do |compiler|
    compiler.plan_signature(plan_name)
  end

  if plan.nil?
    raise Bolt::CLIError, Bolt::Error.unknown_plan(plan_name)
  end

  elements = plan.params_type.elements
  {
    'name' => plan_name,
    'parameters' =>
      unless elements.nil? || elements.empty?
        elements.map { |e|
          p = {
            'name' => e.name,
            'type' => e.value_type
          }
          # TODO: when the default value can be obtained use the actual value instead of nil
          p['default_value'] = nil if e.key_type.is_a?(Puppet::Pops::Types::POptionalType)
          p
        }
      end
  }
end

#get_task_info(task_name) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/bolt/pal.rb', line 141

def get_task_info(task_name)
  task = in_bolt_compiler do |compiler|
    compiler.task_signature(task_name)
  end

  if task.nil?
    raise Bolt::CLIError, Bolt::Error.unknown_task(task_name)
  end

  task.task_hash
end

#in_bolt_compiler(opts = []) ⇒ Object

Runs a block in a PAL script compiler configured for Bolt. Catches exceptions thrown by the block and re-raises them ensuring they are Bolt::Errors since the script compiler block will squash all exceptions.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/bolt/pal.rb', line 61

def in_bolt_compiler(opts = [])
  Puppet.initialize_settings(opts)
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: [BOLTLIB_PATH] + @config[:modulepath], facts: {}) do |pal|
    pal.with_script_compiler do |compiler|
      add_target_spec(compiler)
      begin
        yield compiler
      rescue Puppet::PreformattedError => err
        # Puppet sometimes rescues exceptions notes the location and reraises
        # For now return the original error. Exception cause support was added in Ruby 2.1
        # so we fall back to reporting the error we got for Ruby 2.0.
        if err.respond_to?(:cause) && err.cause
          if err.cause.is_a? Bolt::Error
            err.cause
          else
            e = Bolt::CLIError.new(err.cause.message)
            e.set_backtrace(err.cause.backtrace)
            e
          end
        else
          e = Bolt::CLIError.new(err.message)
          e.set_backtrace(err.backtrace)
          e
        end
      rescue StandardError => err
        e = Bolt::CLIError.new(err.message)
        e.set_backtrace(err.backtrace)
        e
      end
    end
  end

  if r.is_a? StandardError
    raise r
  end
  r
end

#in_plan_compiler(executor, inventory) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/bolt/pal.rb', line 103

def in_plan_compiler(executor, inventory)
  with_bolt_executor(executor, inventory) do
    with_puppet_settings do |opts|
      in_bolt_compiler(opts) do |compiler|
        yield compiler
      end
    end
  end
end

#in_task_compiler(executor, inventory) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/bolt/pal.rb', line 113

def in_task_compiler(executor, inventory)
  with_bolt_executor(executor, inventory) do
    in_bolt_compiler do |compiler|
      yield compiler
    end
  end
end

#list_plansObject



153
154
155
156
157
# File 'lib/bolt/pal.rb', line 153

def list_plans
  in_bolt_compiler do |compiler|
    compiler.list_plans.map { |plan| [plan.name] }.sort
  end
end

#list_tasksObject



131
132
133
134
135
136
137
138
139
# File 'lib/bolt/pal.rb', line 131

def list_tasks
  in_bolt_compiler do |compiler|
    tasks = compiler.list_tasks
    tasks.map(&:name).sort.map do |task_name|
      task_sig = compiler.task_signature(task_name)
      [task_name, task_sig.task.description]
    end
  end
end

#run_plan(object, params, executor = nil, inventory = nil) ⇒ Object



192
193
194
195
196
# File 'lib/bolt/pal.rb', line 192

def run_plan(object, params, executor = nil, inventory = nil)
  in_plan_compiler(executor, inventory) do |compiler|
    compiler.call_function('run_plan', object, params)
  end
end

#run_task(object, targets, params, executor, inventory, &eventblock) ⇒ Object



186
187
188
189
190
# File 'lib/bolt/pal.rb', line 186

def run_task(object, targets, params, executor, inventory, &eventblock)
  in_task_compiler(executor, inventory) do |compiler|
    compiler.call_function('run_task', object, targets, params, &eventblock)
  end
end

#with_bolt_executor(executor, inventory, &block) ⇒ Object



99
100
101
# File 'lib/bolt/pal.rb', line 99

def with_bolt_executor(executor, inventory, &block)
  Puppet.override({ bolt_executor: executor, bolt_inventory: inventory }, &block)
end

#with_puppet_settingsObject



121
122
123
124
125
126
127
128
129
# File 'lib/bolt/pal.rb', line 121

def with_puppet_settings
  Dir.mktmpdir('bolt') do |dir|
    cli = []
    Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
      cli << "--#{setting}" << dir
    end
    yield cli
  end
end