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
# 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)
    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
    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)


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/jackal-nellie/processor.rb', line 67

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)
  unless(payload.get(:data, :nellie, :result, :failed))
    payload.set(:data, :nellie, :result, :complete, true)
  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)


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/jackal-nellie/processor.rb', line 141

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, {}))
    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



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/jackal-nellie/processor.rb', line 166

def fetch_code(payload)
  repository_path = File.join(
    working_directory,
    payload[:id],
    payload.get(:data, :code_fetcher, :asset)
  )
  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))
  asset_store.unpack(
    asset_store.get(payload.get(:data, :code_fetcher, :asset)),
    repository_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)



95
96
97
98
99
100
101
102
103
104
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
132
133
134
# File 'lib/jackal-nellie/processor.rb', line 95

def run_commands(commands, env, payload, process_cwd)
  results = []
  commands.each do |command|
    process_manager.process(payload[:id], command) do |process|
      result = Smash.new
      stdout = process_manager.create_io_tmp(Celluloid.uuid, 'stdout')
      stderr = process_manager.create_io_tmp(Celluloid.uuid, '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
      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

#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