Module: LeapCli::Remote::LeapPlugin

Defined in:
lib/leap_cli/remote/leap_plugin.rb

Instance Method Summary collapse

Instance Method Details

#assert_initializedObject

echos “ok” if the node has been initialized and the required packages are installed, bails out otherwise.



27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/leap_cli/remote/leap_plugin.rb', line 27

def assert_initialized
  begin
    test_initialized_file = "test -f #{INITIALIZED_FILE}"
    check_required_packages = "! dpkg-query -W --showformat='${Status}\n' #{required_packages} 2>&1 | grep -q -E '(deinstall|no packages)'"
    run "#{test_initialized_file} && #{check_required_packages} && echo ok"
  rescue Capistrano::CommandError => exc
    LeapCli::Util.bail! do
      exc.hosts.each do |host|
        LeapCli::Util.log :error, "running deploy: node not initialized. Run 'leap node init #{host}'", :host => host
      end
    end
  end
end

#capture(cmd, &block) ⇒ Object

like stream, but capture all the output before returning



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/leap_cli/remote/leap_plugin.rb', line 111

def capture(cmd, &block)
  command = '%s 2>&1; echo "exitcode=$?" 2>&1;' % cmd
  host_data = {}
  run(command) do |channel, stream, data|
    host_data[channel[:host]] ||= ""
    if data =~ /exitcode=(\d+)\n/
      exitcode = $1.to_i
      data.sub!(/exitcode=(\d+)\n/,'')
      host_data[channel[:host]] += data
      yield({:host => channel[:host], :data => host_data[channel[:host]], :exitcode => exitcode})
    else
      host_data[channel[:host]] += data
    end
  end
end

#check_for_no_deployObject

bails out the deploy if the file /etc/leap/no-deploy exists. This kind of sucks, because it would be better to skip over nodes that have no-deploy set instead halting the entire deploy. As far as I know, with capistrano, there is no way to close one of the ssh connections in the pool and make sure it gets no further commands.



47
48
49
50
51
52
53
54
55
56
57
# File 'lib/leap_cli/remote/leap_plugin.rb', line 47

def check_for_no_deploy
  begin
    run "test ! -f /etc/leap/no-deploy"
  rescue Capistrano::CommandError => exc
    LeapCli::Util.bail! do
      exc.hosts.each do |host|
        LeapCli::Util.log "Can't continue because file /etc/leap/no-deploy exists", :host => host
      end
    end
  end
end

#log(*args, &block) ⇒ Object



12
13
14
# File 'lib/leap_cli/remote/leap_plugin.rb', line 12

def log(*args, &block)
  LeapCli::Util::log(*args, &block)
end

#mark_initializedObject



59
60
61
# File 'lib/leap_cli/remote/leap_plugin.rb', line 59

def mark_initialized
  run "touch #{INITIALIZED_FILE}"
end

#mkdirs(*dirs) ⇒ Object

creates directories that are owned by root and 700 permissions

Raises:

  • (ArgumentError)


19
20
21
22
# File 'lib/leap_cli/remote/leap_plugin.rb', line 19

def mkdirs(*dirs)
  raise ArgumentError.new('illegal dir name') if dirs.grep(/[\' ]/).any?
  run dirs.collect{|dir| "mkdir -m 700 -p #{dir}; "}.join
end

#required_packagesObject



8
9
10
# File 'lib/leap_cli/remote/leap_plugin.rb', line 8

def required_packages
  "puppet ruby-hiera-puppet rsync lsb-release locales"
end

#run_with_progress(cmd, &block) ⇒ Object

Run a command, with a nice status report and progress indicator. Only successful results are returned, errors are printed.

For each successful run on each host, block is yielded with a hash like so:

=> ‘bluejay’, :exitcode => 0, :data => ‘shell output’



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/leap_cli/remote/leap_plugin.rb', line 135

def run_with_progress(cmd, &block)
  ssh_failures = []
  exitcode_failures = []
  succeeded = []
  task = LeapCli.log_level > 1 ? :standard_task : :skip_errors_task
  with_task(task) do
    log :querying, 'facts' do
      progress "   "
      call_on_failure do |host|
        ssh_failures << host
        progress 'F'
      end
      capture(cmd) do |response|
        if response[:exitcode] == 0
          progress '.'
          yield response
        else
          exitcode_failures << response
          progress 'F'
        end
      end
    end
  end
  puts "done"
  if ssh_failures.any?
    log :failed, 'to connect to nodes: ' + ssh_failures.join(' ')
  end
  if exitcode_failures.any?
    log :failed, 'to run successfully:' do
      exitcode_failures.each do |response|
        log "[%s] exit %s - %s" % [response[:host], response[:exitcode], response[:data].strip]
      end
    end
  end
rescue Capistrano::RemoteError => err
  log :error, err.to_s
end

#stream(cmd, &block) ⇒ Object

similar to run(cmd, &block), but with:

  • exit codes

  • stdout and stderr are combined



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/leap_cli/remote/leap_plugin.rb', line 96

def stream(cmd, &block)
  command = '%s 2>&1; echo "exitcode=$?"' % cmd
  run(command) do |channel, stream, data|
    exitcode = nil
    if data =~ /exitcode=(\d+)\n/
      exitcode = $1.to_i
      data.sub!(/exitcode=(\d+)\n/,'')
    end
    yield({:host => channel[:host], :data => data, :exitcode => exitcode})
  end
end

#with_task(name) ⇒ Object

This is a hairy ugly hack, exactly the kind of stuff that makes ruby dangerous and too much fun for its own good.

In most places, we run remote ssh without a current ‘task’. This works fine, except that in a few places, the behavior of capistrano ssh is controlled by the options of the current task.

We don’t want to create an actual current task, because tasks are no fun and can’t take arguments or return values. So, when we need to configure things that can only be configured in a task, we use this handy hack to fake the current task.

This is NOT thread safe, but could be made to be so with some extra work.



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/leap_cli/remote/leap_plugin.rb', line 78

def with_task(name)
  task = @config.tasks[name]
  @config.class.send(:alias_method, :original_current_task, :current_task)
  @config.class.send(:define_method, :current_task, Proc.new(){ task })
  begin
    yield
  ensure
    @config.class.send(:remove_method, :current_task)
    @config.class.send(:alias_method, :current_task, :original_current_task)
  end
end