Class: OctocatalogDiff::Catalog::Computed

Inherits:
OctocatalogDiff::Catalog show all
Defined in:
lib/octocatalog-diff/catalog/computed.rb

Overview

Represents a Puppet catalog that is computed (via ‘puppet master –compile …`) By instantiating this class, the catalog is computed.

Instance Attribute Summary

Attributes inherited from OctocatalogDiff::Catalog

#built, #catalog, #catalog_json, #node, #options

Instance Method Summary collapse

Methods inherited from OctocatalogDiff::Catalog

#build, #builder, #compilation_dir=, create, #error_message, #error_message=, #resource, #resources, #retries, #valid?, #validate_references

Constructor Details

#initialize(options) ⇒ Computed

Constructor

Parameters:

  • :node (String)

    REQUIRED: Node name

  • :basedir (String)

    Directory in which to compile the catalog

  • :pass_env_vars (Array<String>)

    Environment variables to pass when compiling catalog

  • :retry_failed_catalog (Integer)

    Number of retries if a catalog compilation fails

  • :tag (String)

    For display purposes, the catalog being compiled

  • :puppet_binary (String)

    Full path to Puppet

  • :puppet_version (String)

    Puppet version (optional; if not supplied, it is calculated)

  • :puppet_command (String)

    Full command to run Puppet (optional; if not supplied, it is calculated)

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/octocatalog-diff/catalog/computed.rb', line 30

def initialize(options)
  super

  raise ArgumentError, 'Node name must be passed to OctocatalogDiff::Catalog::Computed' unless options[:node].is_a?(String)
  raise ArgumentError, 'Branch is undefined' unless options[:branch]

  # Additional class variables
  @pass_env_vars = options.fetch(:pass_env_vars, [])
  @retry_failed_catalog = options.fetch(:retry_failed_catalog, 0)
  @tag = options.fetch(:tag, 'catalog')
  @puppet_binary = options[:puppet_binary]
  @puppet_version = options[:puppet_version]
  @puppet_command = options[:puppet_command]
  @builddir = nil
  @facts_terminus = options.fetch(:facts_terminus, 'yaml')
end

Instance Method Details

#assert_that_puppet_environment_directory_existsObject

Private method: Make sure that the Puppet environment directory exists.

Raises:

  • (Errno::ENOENT)


193
194
195
196
197
# File 'lib/octocatalog-diff/catalog/computed.rb', line 193

def assert_that_puppet_environment_directory_exists
  target_dir = File.join(@builddir.tempdir, 'environments', environment)
  return if File.directory?(target_dir)
  raise Errno::ENOENT, "Environment directory #{target_dir} does not exist"
end

#bootstrap(logger) ⇒ Object

Private method: Bootstrap a directory



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/octocatalog-diff/catalog/computed.rb', line 74

def bootstrap(logger)
  return if @builddir

  # Fill options for creating and populating the temporary directory
  tmphash = @options.dup

  # Bootstrap directory if needed
  if !@options[:bootstrapped_dir].nil?
    raise Errno::ENOENT, "Invalid dir #{@options[:bootstrapped_dir]}" unless File.directory?(@options[:bootstrapped_dir])
    tmphash[:basedir] = @options[:bootstrapped_dir]
  elsif @options[:branch] == '.'
    if @options[:bootstrap_current]
      tmphash[:basedir] = OctocatalogDiff::Util::Util.temp_dir('ocd-bootstrap-basedir-')
      FileUtils.cp_r File.join(@options[:basedir], '.'), tmphash[:basedir]

      o = @options.reject { |k, _v| k == :branch }.merge(path: tmphash[:basedir])
      OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(o, logger)
    else
      tmphash[:basedir] = @options[:basedir]
    end
  else
    tmphash[:basedir] = OctocatalogDiff::Util::Util.temp_dir('ocd-bootstrap-checkout-')
    OctocatalogDiff::CatalogUtil::Bootstrap.bootstrap_directory(@options.merge(path: tmphash[:basedir]), logger)
  end

  # Create and populate the temporary directory
  @builddir ||= OctocatalogDiff::CatalogUtil::BuildDir.new(tmphash, logger)
end

#build_catalog(logger) ⇒ Object

Private method: Build catalog by running Puppet

Parameters:

  • logger (Logger)

    Logger object



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
# File 'lib/octocatalog-diff/catalog/computed.rb', line 105

def build_catalog(logger)
  if @facts_terminus != 'facter'
    facts_obj = OctocatalogDiff::CatalogUtil::Facts.new(@options, logger)
    logger.debug "Start retrieving facts for #{@node} from #{self.class}"
    @options[:facts] = facts_obj.facts
    logger.debug "Success retrieving facts for #{@node} from #{self.class}"
  end

  bootstrap(logger)
  result = run_puppet(logger)
  @retries = result[:retries]
  if (result[:exitcode]).zero?
    begin
      @catalog = ::JSON.parse(result[:stdout])
      @catalog_json = result[:stdout]
      @error_message = nil
    rescue ::JSON::ParserError => exc
      @catalog = nil
      @catalog_json = nil
      @error_message = "Catalog has invalid JSON: #{exc.message}"
    end
  else
    @error_message = result[:stderr]
    @catalog = nil
    @catalog_json = nil
  end
end

#compilation_dirString

Compilation directory

Returns:

  • (String)

    Compilation directory



56
57
58
59
# File 'lib/octocatalog-diff/catalog/computed.rb', line 56

def compilation_dir
  raise 'Catalog was not built' if @builddir.nil?
  @builddir.tempdir
end

#convert_file_resources(dry_run = false) ⇒ Object

Convert file resources source => “puppet:///…” to content => “actual content of file”.



67
68
69
70
71
# File 'lib/octocatalog-diff/catalog/computed.rb', line 67

def convert_file_resources(dry_run = false)
  return @options.key?(:basedir) if dry_run
  return false unless @options[:basedir]
  OctocatalogDiff::CatalogUtil::FileResources.convert_file_resources(self, environment)
end

#environmentObject

Environment used to compile catalog



62
63
64
# File 'lib/octocatalog-diff/catalog/computed.rb', line 62

def environment
  @options.fetch(:environment, 'production')
end

#exec_puppet(logger) ⇒ Hash

Private method: Actually execute puppet

Returns:

  • (Hash)

    { stdout, stderr, exitcode }



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
# File 'lib/octocatalog-diff/catalog/computed.rb', line 158

def exec_puppet(logger)
  # This is the environment provided to the puppet command.
  env = {}
  @pass_env_vars.each { |var| env[var] ||= ENV[var] }

  # This is the Puppet command itself
  env['OCD_PUPPET_BINARY'] = @puppet_command_obj.puppet_binary

  # Additional passed-in options
  sr_run_opts = env.merge(
    logger: logger,
    working_dir: @builddir.tempdir,
    argv: @puppet_command_obj.puppet_argv
  )

  # Set up the ScriptRunner
  scriptrunner = OctocatalogDiff::Util::ScriptRunner.new(
    default_script: 'puppet/puppet.sh',
    override_script_path: @options[:override_script_path]
  )

  begin
    scriptrunner.run(sr_run_opts)
  rescue OctocatalogDiff::Util::ScriptRunner::ScriptException => exc
    logger.warn "Puppet command failed: #{exc.message}" if logger
  end

  {
    stdout: scriptrunner.stdout,
    stderr: scriptrunner.stderr,
    exitcode: scriptrunner.exitcode
  }
end

#puppet_commandString

Get the command to compile the catalog

Returns:

  • (String)

    Puppet command line



135
136
137
# File 'lib/octocatalog-diff/catalog/computed.rb', line 135

def puppet_command
  puppet_command_obj.puppet_command
end

#puppet_command_objObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/octocatalog-diff/catalog/computed.rb', line 139

def puppet_command_obj
  @puppet_command_obj ||= begin
    raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary

    command_opts = @options.merge(
      node: @node,
      compilation_dir: @builddir.tempdir,
      parser: @options.fetch(:parser, :default),
      puppet_binary: @puppet_binary,
      fact_file: @builddir.fact_file,
      dir: @builddir.tempdir,
      enc: @builddir.enc
    )
    OctocatalogDiff::CatalogUtil::Command.new(command_opts)
  end
end

#puppet_versionString

Get the Puppet version

Returns:

Raises:

  • (ArgumentError)


49
50
51
52
# File 'lib/octocatalog-diff/catalog/computed.rb', line 49

def puppet_version
  raise ArgumentError, '"puppet_binary" was not passed to OctocatalogDiff::Catalog::Computed' unless @puppet_binary
  @puppet_version ||= OctocatalogDiff::Util::PuppetVersion.puppet_version(@puppet_binary, @options)
end

#run_puppet(logger) ⇒ Hash

Private method: Runs puppet on the command line to compile the catalog Exit code is 0 if catalog generation was successful, non-zero otherwise.

Parameters:

  • logger (Logger)

    Logger object

Returns:

  • (Hash)

    { stdout: <catalog as JSON>, stderr: <error messages>, exitcode: <hopefully 0> }



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/octocatalog-diff/catalog/computed.rb', line 203

def run_puppet(logger)
  assert_that_puppet_environment_directory_exists

  # Run 'cmd' with environment 'env' from directory 'dir'
  # First line of a successful result needs to be stripped off. It will look like:
  # Notice: Compiled catalog for xxx in environment production in 27.88 seconds
  retval = {}
  0.upto(@retry_failed_catalog) do |retry_num|
    @retries = retry_num
    time_begin = Time.now
    logger.debug("(#{@tag}) Try #{1 + retry_num} executing Puppet #{puppet_version}: #{puppet_command}")
    result = exec_puppet(logger)

    # Success
    if (result[:exitcode]).zero?
      logger.debug("(#{@tag}) Catalog succeeded on try #{1 + retry_num} in #{Time.now - time_begin} seconds")
      first_brace = result[:stdout].index('{') || 0
      retval = {
        stdout: result[:stdout][first_brace..-1],
        stderr: nil,
        exitcode: 0,
        retries: retry_num
      }
      break
    end

    # Failure
    logger.debug("(#{@tag}) Catalog failed on try #{1 + retry_num} in #{Time.now - time_begin} seconds")
    retval = result.merge(retries: retry_num)
  end
  retval
end