Class: Jackal::Nellie::Processor

Inherits:
Callback
  • Object
show all
Defined in:
lib/jackal-nellie/processor.rb

Overview

Command processor

Constant Summary collapse

DEFAULT_SCRIPT_NAME =

Default nellie file name

'.nellie'
DEFAULT_WORKING_DIRECTORY =

Default working directory

'/tmp/nellie'

Instance Method Summary collapse

Instance Method Details

#execute(message) ⇒ Object

Run nellie!

Parameters:

  • message (Carnivore::Message)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/jackal-nellie/processor.rb', line 46

def execute(message)
  failure_wrap(message) do |payload|
    debug "Processing nellie payload!"
    nellie_cwd = fetch_code(payload)
    begin
      unless(payload.get(:data, :nellie, :commands))
        extract_nellie_commands(nellie_cwd, payload)
      end
      if(payload.get(:data, :nellie, :commands))
        execute_commands(nellie_cwd, payload)
      else
        warn "No nellie commands found for execution on message! (#{message})"
      end
    ensure
      debug "Removing nellie job working directory: `#{nellie_cwd}`"
      FileUtils.rm_rf(File.dirname(nellie_cwd))
    end
    job_completed(:nellie, payload, message)
  end
end

#execute_commands(nellie_cwd, payload) ⇒ TrueClass

Execute commands

Parameters:

  • nellie_cwd (String)

    repository directory

  • payload (Smash)

Returns:

  • (TrueClass)


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
98
99
100
# File 'lib/jackal-nellie/processor.rb', line 72

def execute_commands(nellie_cwd, payload)
  process_environment = payload.fetch(:data, :nellie, :environment, Smash.new).merge(
    Smash.new(
      'NELLIE_GIT_COMMIT_SHA' => payload.get(:data, :code_fetcher, :info, :commit_sha),
      'NELLIE_GIT_REF' => payload.get(:data, :code_fetcher, :info, :reference)
    )
  )
  commands = [payload.get(:data, :nellie, :commands)].flatten.compact
  results = run_commands(commands, process_environment, payload, nellie_cwd)
  payload.set(:data, :nellie, :history, results)
  payload[:data][:nellie].delete(:commands)
  payload[:data][:nellie].delete(:environment)
  if(payload.get(:data, :nellie, :cleanup))
    debug "Cleanup commands detected. Running now."
    cleanup_results = run_commands(payload[:data][:nellie].delete(:cleanup), process_environment, payload, nellie_cwd)
    payload.set(:data, :nellie, :cleanup_history, cleanup_results)
    debug "Cleanup commands have completed."
  end
  unless(payload.get(:data, :nellie, :result, :failed))
    payload.set(:data, :nellie, :result, :complete, true)
    if(payload.get(:data, :nellie, :release_assets))
      set_github_release_assets(payload, nellie_cwd)
    end
  end
  payload.set(:data, :nellie, :status,
    payload.get(:data, :nellie, :result, :complete) ? 'success' : 'error'
  )
  true
end

#extract_nellie_commands(nellie_cwd, payload) ⇒ TrueClass, FalseClass

Extract nellie commands from repository file

Parameters:

  • nellie_cwd (String)

    path to repository directory

  • payload (Smash)

Returns:

  • (TrueClass, FalseClass)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/jackal-nellie/processor.rb', line 199

def extract_nellie_commands(nellie_cwd, payload)
  script_path = File.join(nellie_cwd, nellie_script_name)
  if(File.exists?(script_path))
    begin
      nellie_cmds = MultiJson.load(File.read(script_path)).to_smash #Bogo::Config.new(script_path).data
      debug "Nellie file is structured data. Populating commands into payload. (#{script_path})"
      payload[:data].set(:nellie, :commands, nellie_cmds[:commands])
      payload[:data].set(:nellie, :environment, nellie_cmds.fetch(:environment, {}))
      payload[:data].set(:nellie, :cleanup, Array(nellie_cmds[:cleanup])) if nellie_cmds[:cleanup]
      payload[:data].set(:nellie, :release_assets, nellie_cmds[:release]) if nellie_cmds[:release]
    rescue => e
      debug "Parsing nellie file failed. Assuming direct execution. (#{script_path})"
      payload[:data].set(:nellie, :commands,
        File.executable?(script_path) ? script_path : "/bin/bash #{script_path}"
      )
    end
    true
  else
    debug "Failed to locate nellie command file at: #{script_path}"
    false
  end
end

#fetch_code(payload) ⇒ String

Fetch code from asset store and unpack for local usage

Parameters:

  • payload (Smash)

Returns:

  • (String)

    directory path



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/jackal-nellie/processor.rb', line 226

def fetch_code(payload)
  repository_path = File.join(
    working_directory,
    payload[:id],
    payload.get(:data, :code_fetcher, :asset).sub(/\..*$/, '')
  )
  if(File.directory?(repository_path))
    warn "Existing path detected for repository unpack. Removing! (#{repository_path})"
    FileUtils.rm_rf(repository_path)
  end
  FileUtils.mkdir_p(File.dirname(repository_path))
  code_asset = asset_store.get(payload.get(:data, :code_fetcher, :asset))
  packed_code_asset_path = File.join(working_directory, "code-asset-#{payload[:id]}.zip")
  FileUtils.cp(code_asset.path, packed_code_asset_path)
  packed_code_asset = File.open(packed_code_asset_path, 'rb')
  asset_store.unpack(
    packed_code_asset,
    repository_path
  )
  File.unlink(packed_code_asset_path)
  repository_path
end

#nellie_script_nameString

Returns nellie command file name.

Returns:

  • (String)

    nellie command file name



28
29
30
# File 'lib/jackal-nellie/processor.rb', line 28

def nellie_script_name
  config.fetch(:script_name, DEFAULT_SCRIPT_NAME)
end

#run_commands(commands, env, payload, process_cwd) ⇒ Array<Smash>

Run collection of commands

Parameters:

  • commands (Array<String>)

    commands to execute

  • env (Hash)

    environment variables for process

  • payload (Smash)
  • process_cwd (String)

    working directory for process

Returns:

  • (Array<Smash>)

    command results (:stop_time, :exit_code, :logs, :timed_out)



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
# File 'lib/jackal-nellie/processor.rb', line 150

def run_commands(commands, env, payload, process_cwd)
  results = []
  commands.each do |command|
    process_manager.process(payload[:id], command) do |process|
      info "Running command `#{command}` in `#{process_cwd}`"
      result = Smash.new
      stdout = process_manager.create_io_tmp(payload[:id], 'stdout')
      stderr = process_manager.create_io_tmp(payload[:id], 'stderr')
      process.io.stdout = stdout
      process.io.stderr = stderr
      process.cwd = process_cwd
      process.environment.replace(env.dup)
      process.leader = true
      result[:start_time] = Time.now.to_i
      process.start
      begin
        process.poll_for_exit(config.fetch(:max_execution_time, 60))
      rescue ChildProcess::TimeoutError
        process.stop
        result[:timed_out] = true
      end
      result[:stop_time] = Time.now.to_i
      result[:exit_code] = process.exit_code
      [stdout, stderr].each do |io|
        key = "nellie/#{File.basename(io.path)}"
        type = io.path.split('-').last
        io.rewind
        asset_store.put(key, io)
        result.set(:logs, type, key)
        io.close
        File.delete(io.path)
      end
      info "Completed command `#{command}` in `#{process_cwd}`. " \
        "Runtime: #{result[:stop_time] - result[:start_time]} seconds"
      results << result
      unless(process.exit_code == 0)
        payload.set(:data, :nellie, :result, :failed, true)
      end
    end
    break if payload.get(:data, :nellie, :result, :failed)
  end
  results
end

#set_github_release_assets(payload, nellie_cwd) ⇒ NilClass

Populate for pushing github release assets

Parameters:

  • payload (Smash)
  • nellie_cwd (String)

    working directory of repo

Returns:

  • (NilClass)


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
# File 'lib/jackal-nellie/processor.rb', line 107

def set_github_release_assets(payload, nellie_cwd)
  release_assets = Dir.glob(File.join(nellie_cwd, payload.get(:data, :nellie, :release_assets)))
  unless(release_assets.empty?)
    debug "Release assets detected: #{release_assets}"
    release_asset_keys = release_assets.map do |asset|
      asset_key = "nellie/release-assets/#{payload[:id]}/#{File.basename(asset)}"
      asset_store.put(asset_key, File.open(asset, 'rb'))
      asset_key
    end
    if(payload.get(:data, :code_fetcher, :info, :ref).to_s.include?('/tags/'))
      version = payload.get(:data, :code_fetcher, :info, :ref).to_s.sub(/^.*\/tags\//, '')
    else
      version = payload.get(:data, :code_fetcher, :info, :commit_sha).to_s[0,6]
      prerelease = true
    end
    payload.set(:data, :github_kit, :release,
      Smash.new(
        :repository => [
          payload.get(:data, :code_fetcher, :info, :owner),
          payload.get(:data, :code_fetcher, :info, :name)
        ].join('/'),
        :reference => payload.get(:data, :code_fetcher, :info, :commit_sha),
        :tag_name => version,
        :name => [
          payload.get(:data, :code_fetcher, :info, :name),
          version
        ].join('-'),
        :body => "Release - #{payload.get(:data, :code_fetcher, :info, :name)} <#{version}>",
        :prerelease => prerelease,
        :assets => release_asset_keys
      )
    )
    nil
  end
end

#setup(*_) ⇒ Object

Setup callback



14
15
16
# File 'lib/jackal-nellie/processor.rb', line 14

def setup(*_)
  require 'fileutils'
end

#valid?(message) ⇒ Truthy, Falsey

Determine validity of message

Parameters:

  • message (Carnivore::Message)

Returns:

  • (Truthy, Falsey)


36
37
38
39
40
41
# File 'lib/jackal-nellie/processor.rb', line 36

def valid?(message)
  super do |payload|
    payload.get(:data, :code_fetcher, :asset) &&
      !payload.get(:data, :nellie, :result)
  end
end

#working_directoryString

Returns working directory.

Returns:

  • (String)

    working directory



19
20
21
22
23
24
25
# File 'lib/jackal-nellie/processor.rb', line 19

def working_directory
  memoize(:working_directory) do
    wd = config.fetch(:working_directory, DEFAULT_WORKING_DIRECTORY)
    FileUtils.mkdir_p(wd)
    wd
  end
end