Class: Configure

Inherits:
Command show all
Defined in:
lib/maws/commands/configure.rb

Instance Attribute Summary collapse

Attributes inherited from Command

#connection, #maws

Instance Method Summary collapse

Methods inherited from Command

#add_generic_options, #initialize, #instances, #process_options, #verify_options

Constructor Details

This class inherits a constructor from Command

Instance Attribute Details

#optionsObject (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/maws/commands/configure.rb', line 11

def options
  @options
end

Instance Method Details

#add_specific_options(parser) ⇒ Object



16
17
18
19
20
21
22
23
# File 'lib/maws/commands/configure.rb', line 16

def add_specific_options(parser)
  parser.opt :dump, "Dump config files before uploading them", :type => :flag, :default => false
  parser.opt :command, "Command to run remotely (either name or a string)", :type => :string, :default => ""
  parser.opt :login_name, "The SSH login name", :short => '-l', :type => :string, :default => "root"
  parser.opt :hostname, "The SSH hostname", :short => '-h', :type => :string, :default => nil
  parser.opt :identity_file, "The SSH identity file", :short => '-i', :type => :string
  parser.opt :copy_to_local, "Copy template output to local folders", :short => '-L', :type => :flag, :default => false
end

#build_ssh_actions_for_instance(instance) ⇒ Object



83
84
85
86
87
88
89
90
91
# File 'lib/maws/commands/configure.rb', line 83

def build_ssh_actions_for_instance(instance)
  if instance.configurations && instance.configurations.collect{|c| c.name}.include?(options.command)
    # command specified as name of a config
    configuration = instance.configurations.find{|c| c.name == options.command}
    execute_configuration(instance, configuration)
  elsif options.command && !options.command.empty?
    queue_remote_command(instance, nil, options.command)
  end
end

#descriptionObject



12
13
14
# File 'lib/maws/commands/configure.rb', line 12

def description
  "configure - create and upload templated configurations and run commands on specified servers with SSH"
end

#do_remote_command(ssh, instance, name, command) ⇒ Object



136
137
138
139
140
141
142
143
144
145
# File 'lib/maws/commands/configure.rb', line 136

def do_remote_command(ssh, instance, name, command)
  if name
    ensure_output :info, "   executing #{name} command: " + command
  else
    ensure_output :info, "   executing #{command}"
  end

  result = ssh.exec!(command)
  ensure_output :info, result if result
end

#execute_configuration(instance, configuration) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/maws/commands/configure.rb', line 119

def execute_configuration(instance, configuration)
  if configuration.template
    generate_and_queue_upload_template(instance, configuration)
  elsif configuration.command
    queue_remote_command(instance, configuration.name, configuration.command)
  elsif configuration.command_set
    configuration.command_set.to_a.each do |command_name|
      specified_configuration = instance.configurations.find{|c| c.name == command_name}
      execute_configuration(instance, specified_configuration) if specified_configuration
    end
  end
end

#execute_ssh_actions(instances) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/maws/commands/configure.rb', line 48

def execute_ssh_actions(instances)
  # create threads that execute ssh commands
  threads = []
  ssh_connections = {}
  instances.each do |instance|
    ssh_actions = @ssh_actions[instance]
    next if ssh_actions.empty?

    threads << Thread.new do
      Thread.current[:title] = "#{instance.name} (#{instance.dns_name})"
      Thread.current[:ensured_output] = []

      ssh = ssh_connect_to(instance)
      ssh_connections[instance] = ssh

      ssh_actions.each {|action| action.call(ssh)}

      ssh_disconnect(ssh, instance)
      ssh_connections[instance] = nil
    end
  end

  begin
    threads.each do |t|
      Kernel.sleep 0.03
      t.join
    end
  ensure
    ssh_connections.each do |instance, ssh|
      ssh_disconnect(ssh, instance) if ssh
    end
    print_ensured_output(threads)
  end
end

#generate_and_queue_upload_template(instance, configuration) ⇒ Object



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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/maws/commands/configure.rb', line 147

def generate_and_queue_upload_template(instance, configuration)
  locations = [configuration.location].flatten
  locations.each do |location|
    # prepare params for config file interpolation
    resolved_params = {}
    configuration.template_params ||= {}

    configuration.template_params.each do |param_name, param_config|
      resolved_params[param_name] = resolve_template_param(instance, configuration.template, param_name, param_config)
    end
    resolved_params['instance'] = instance
    resolved_params['instances'] = instances
    resolved_params['settings'] = @profile.settings

    # generate config file
    template_path = File.join(TEMPLATES_PATH, configuration.template + ".erb")
    template = File.read(template_path)
    generated_config =  Erubis::Eruby.new(template).result(resolved_params)

    Dir.mkdir(TEMPLATE_OUTPUT_DIR) if !File.directory?(TEMPLATE_OUTPUT_DIR)
    config_output_path = File.join(TEMPLATE_OUTPUT_DIR, "#{instance.name}--#{instance.aws_id}." + configuration.template)
    File.open(config_output_path, "w") {|f| f.write(generated_config)}
    info "generated  '#{config_output_path}'"

    if options.copy_to_local
      info "copying to local path: #{location}"
      FileUtils.cp(config_output_path, location)
    end

    queue_ssh_action(instance) do |ssh|
      ensure_output :info, "configuring #{configuration.template} for #{instance.name}"

      if options.dump
        ensure_output :info, "\n\n------- BEGIN #{config_output_path} -------"
        ensure_output :info, generated_config
        ensure_output :info, "-------- END #{config_output_path} --------\n\n"
      end

      if configuration.copy_as_user
        remote_tmp_path = File.join("/tmp/", "#{instance.name}--#{instance.aws_id}." + configuration.template)
        ssh.scp.upload!(config_output_path, remote_tmp_path)
        cp_command = "sudo su - #{configuration.copy_as_user} -c 'cp -f #{remote_tmp_path} #{location}' && sudo rm #{remote_tmp_path}"
        do_remote_command(ssh, instance, 'copy_as_user', cp_command)
      else
        ssh.scp.upload!(config_output_path, location)
      end

      if configuration.owner
        chown_command = "sudo su - -c 'chown -R #{configuration.owner} #{location}'"
        do_remote_command(ssh, instance, 'permissions', chown_command)
      end

      if configuration.permissions
        chmod_command = "sudo su - -c 'chmod -R #{configuration.permissions} #{location}'"
        do_remote_command(ssh, instance, 'permissions', chmod_command)
      end

      timestamp = ssh.exec!("sudo stat -c %y #{location}")
      ensure_output :info, "            new timestamp for #{location}: " + timestamp
    end
  end
end

#prepare_ssh_actions(instances) ⇒ Object



41
42
43
44
45
46
# File 'lib/maws/commands/configure.rb', line 41

def prepare_ssh_actions(instances)
  instances.each do |instance|
    @ssh_actions[instance] = []
    build_ssh_actions_for_instance(instance)
  end
end

#queue_remote_command(instance, name, command) ⇒ Object



132
133
134
# File 'lib/maws/commands/configure.rb', line 132

def queue_remote_command(instance, name, command)
  queue_ssh_action(instance) {|ssh| do_remote_command(ssh, instance, name, command)}
end

#resolve_template_param(instance, template_name, param_name, param_config) ⇒ Object



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
235
236
237
238
239
240
241
242
243
# File 'lib/maws/commands/configure.rb', line 210

def resolve_template_param(instance, template_name, param_name, param_config)
  if param_config == 'self'
    return instance

  elsif param_config == 'config'
    return @config

  elsif param_config.is_a? String
    return param_config

  elsif param_config.select_one.is_a? String
    context = "#{instance.role_name}-#{template_name}-#{param_name}"
    @chunk_source.select(:chunk, param_config.select_one, instance.zone,
                            :chunk_size => 1, :chunk_key => context).first

  elsif param_config.select_many.is_a? String
    @chunk_source.select(:all, param_config.select_many, instance.zone)

  elsif param_config.select_one
    context = "#{instance.role_name}-#{template_name}-#{param_name}"
    from = param_config.select_one.from # nil means default
    @chunk_source.select(:chunk, param_config.select_one.role, instance.zone,
                                  :chunk_size => 1, :chunk_key => context, :from => from).first

  elsif param_config.select_many
    context = "#{instance.role_name}-#{template_name}-#{param_name}"
    from = param_config.select_many.from # nil means default
    @chunk_source.select(:chunk, param_config.select_many.role, instance.zone,
                                :chunk_size => param_config.select_many.chunk_size, :chunk_key => context, :from => from)

  else
    param_config
  end
end

#run!Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/maws/commands/configure.rb', line 25

def run!
  @chunk_source = ChunkSource.new(@config, instances)
  @template_output_dir = @config.config.paths.template_output

  @ssh_actions = {}
  @options = @config.command_line

  configurable_instances = instances.specified.find_all do |instance|
    (instance.status == 'running' || instance.configure_without_running) &&
    (instance.configurations || !options.command.empty?)
  end

  prepare_ssh_actions(configurable_instances)
  execute_ssh_actions(configurable_instances)
end

#ssh_connect_to(instance) ⇒ Object

Raises:

  • (ArgumentError)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/maws/commands/configure.rb', line 93

def ssh_connect_to(instance)
  host = options.hostname || instance.dns_name
  user = options.

  identity_file = options.identity_file || File.join(KEYPAIRS_PATH, "#{instance.keypair}.pem")
  raise ArgumentError.new("Missing identity file: #{identity_file}") if !File.exists?(identity_file)

  info "connecting to '#{user}@#{host}'..."

  3.times { # retry on host unreachable errors
    begin
      return Net::SSH.start(host, user, { :keys => [identity_file], :verbose => :warn, :auth_methods => ["publickey"], :keys_only => true })
    rescue Errno::EHOSTUNREACH
      sleep 2
    end
  }
end

#ssh_disconnect(ssh, instance) ⇒ Object



111
112
113
114
115
116
117
# File 'lib/maws/commands/configure.rb', line 111

def ssh_disconnect(ssh, instance)
  host = options.hostname || instance.dns_name
  user = options.

  info "...done (disconnected from '#{user}@#{host}')"
  ssh.close
end

#verify_configsObject



245
246
247
248
249
250
251
252
253
# File 'lib/maws/commands/configure.rb', line 245

def verify_configs
  @config.roles.each do |name, config|
    verify_config(name, config, "role definition")
  end

  @config.profile.each do |name, config|
    verify_config(name, config, "profile role")
  end
end