fleetctl 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cd025b42bf812102d80c3ded567acf7a5f3759bd
4
+ data.tar.gz: d51ee078697bbd24bbe9486d99f3521571ac99be
5
+ SHA512:
6
+ metadata.gz: c38239fb5798ad2616d91089e5f5d1633e368c67245fae29b1322fde08a725301af40182f3687a2d62a108df76c206ca9930148192961f8bfe031db7eea4875a
7
+ data.tar.gz: 7ab0ec882dbe5f63584898c5f7495e348e1634adb6bddaf124e19a5cab49f032cdb75fbfa6530c11cea35f17bf49d74253868c09bfc7af413c81035731289eb1
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fleetctl.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josh Lauer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Fleetctl
2
+
3
+ Fleetctl is a gem for remotely controlling a fleet cluster on CoreOS. The gem executes all commands remotely on the fleet hosts via SSH, rather than using the `fleetctl` executable. At the time of this writing, this has proven to be more stable than using the native executable's `--tunnel` flag.
4
+
5
+ Warning: This gem is essentially a wrapper for an executable that is still in alpha. Things are changing rapidly. It is known to work in one specific context. Use at your own risk. Future updates will be more stable.
6
+
7
+ ## Installation and Configuration
8
+
9
+ Add Fleetctl to your Gemfile:
10
+
11
+ gem 'fleetctl', git: 'https://github.com/josh-lauer/ruby-fleetctl', tag: 'v0.1.0'
12
+
13
+ And `$ bundle install`
14
+
15
+ ###Configure Fleetctl:
16
+
17
+ All configuration options in Fleetctl are currently global. Just pass a hash to `Fleetctl.config` with the appropriate options included, like so:
18
+
19
+ Fleetctl.config({
20
+ # insert configuration options here
21
+ })
22
+
23
+ The options, with default values, are as follows:
24
+
25
+ # a hash of global flags to be passed to the fleetctl executable.
26
+ global: {}
27
+
28
+ # the path to the fleetctl executable on the fleet hosts. (go ahead and move or rename it, weirdo)
29
+ executable: 'fleetctl'
30
+
31
+ # the logger for fleet to use. Pass it Rails.logger if you are in a rails application
32
+ logger: Logger.new(STDOUT)
33
+
34
+ # a string or array of strings to be prepended to the fleetctl command to be executed
35
+ # on the remote fleet host. use this if you need to set environment variables and the like.
36
+ command_prefix: nil
37
+
38
+ # a discovery url to use to locate the fleet hosts.
39
+ discovery_url: nil
40
+
41
+ # the IP of any of the fleet hosts to use for discovery
42
+ fleet_host: nil
43
+
44
+ # the user to use when SSH'ing to the fleet hosts
45
+ fleet_user: 'core'
46
+
47
+ # options to pass varbatim to Net::SSH and Net::SCP
48
+ ssh_options: {}
49
+
50
+ # temp directory to be used on the fleet hosts
51
+ remote_temp_dir: '/tmp'
52
+
53
+ At a minumum, either :fleet_host or :discovery_url must be provided in order to contact the cluster. If both are used, the discovery_url will be used as a fallback if the fleet_host specified cannot be reached.
54
+
55
+ ## Usage
56
+ To use Fleetctl, first create a `Fleetctl::Controller`
57
+
58
+ A controller can be instantiated like this...
59
+
60
+ fleet = Fleetctl.new
61
+ => #<Fleet::Controller...>
62
+
63
+ ... or you can use a global singleton instance, like so:
64
+
65
+ fleet = Fleetctl.instance
66
+ => #<Fleet::Controller...>
67
+
68
+ You can also call the methods `:instance`, `:machines`, `:units`, `:[]`, `:sync`, `:start`, `:submit`, `:load`, and `:destroy` on `Fleetctl` directly, and they will be passed to the singleton instance. More on them below.
69
+
70
+ In either case, Fleetctl caches its state in order to avoid repeatedly querying the cluster. When actions that would change the state are taken (such as publishing or deleting units) the cache is refreshed. In order to manually trigger a refresh, call:
71
+
72
+ fleet.sync
73
+ => true
74
+
75
+ To get an array of all the units on the cluster:
76
+
77
+ fleet.units
78
+ => [#<Fleet::Unit...>, #<Fleet::Unit...>, ...]
79
+
80
+ To get an array of all the machines that comprise the cluster:
81
+
82
+ fleet.machines
83
+ => [#<Fleet::Machine...>, #<Fleet::Machine...>, ...]
84
+
85
+ To get a specific unit by name:
86
+
87
+ fleet['my-unit.service']
88
+ => #<Fleet::Unit...>
89
+
90
+ The `:start`, `:load`, and `:submit` methods all operate on one or more `File` objects (fleet unitfiles).
91
+ unitfile = File.open('my-unit.service')
92
+ fleet.submit(unitfile)
93
+ => true
94
+
95
+ To remove one or more units from the cluster, call `:destroy` on the controller, and pass in the name
96
+ or names of the units you wish to destroy
97
+
98
+ fleet.destroy('my-unit.service')
99
+ => true
100
+
101
+ ### Working with units
102
+
103
+ A `Fleet::Unit` represents a unitfile and its accompanying docker container, if any.
104
+
105
+ ### Working with machines
106
+
107
+ A `Fleet::Machine` represents a machine which is part of a fleet cluster.
108
+
109
+ #### More Documentation pending
110
+
111
+ ## Contributing
112
+
113
+ 1. Fork it ( https://github.com/josh-lauer/fleetctl/fork )
114
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
115
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
116
+ 4. Push to the branch (`git push origin my-new-feature`)
117
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/fleetctl.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fleetctl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'fleetctl'
8
+ spec.version = Fleetctl::VERSION
9
+ spec.authors = ['Josh Lauer']
10
+ spec.email = ['jlauer@cloudspace.com']
11
+ spec.summary = %q{A simple wrapper for fleetctl}
12
+ spec.description = %q{Allows controlling fleet clusters via a ruby API}
13
+ spec.homepage = 'https://github.com/josh-lauer/ruby-fleetctl'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake', '~> 10.3'
23
+ spec.add_dependency 'hashie', '= 3.0.0'
24
+ spec.add_dependency 'net-ssh', '= 2.9.1'
25
+ spec.add_dependency 'net-scp', '= 1.2.1'
26
+ end
@@ -0,0 +1,78 @@
1
+ module Fleet
2
+ class Cluster < Fleet::ItemSet
3
+ attr_accessor :controller
4
+
5
+ def initialize(*args, controller: nil)
6
+ @controller = controller
7
+ super(*args)
8
+ end
9
+
10
+ def fleet_hosts
11
+ map(&:ip)
12
+ end
13
+
14
+ def fleet_host
15
+ fleet_hosts.sample
16
+ end
17
+
18
+ def machines
19
+ discover! if empty?
20
+ to_a
21
+ end
22
+
23
+ # attempts to rebuild the cluster from any of the hosts passed as arguments
24
+ # returns the first ip that worked, else nil
25
+ def build_from(*ip_addrs)
26
+ ip_addrs = [*ip_addrs].flatten.compact
27
+ begin
28
+ Fleetctl.logger.info 'building from hosts: ' + ip_addrs.inspect
29
+ built_from = ip_addrs.detect { |ip_addr| fetch_machines(ip_addr) }
30
+ Fleetctl.logger.info 'built successfully from host: ' + built_from.inspect if built_from
31
+ built_from
32
+ rescue => e
33
+ Fleetctl.logger.error 'ERROR building from hosts: ' + ip_addrs.inspect
34
+ Fleetctl.logger.error e.message
35
+ Fleetctl.logger.error e.backtrace.join("\n")
36
+ nil
37
+ end
38
+ end
39
+
40
+ # attempts a list-machines action on the given host.
41
+ # returns true if successful, else false
42
+ def fetch_machines(host)
43
+ Fleetctl.logger.info 'Fetching machines from host: '+host.inspect
44
+ clear
45
+ Fleetctl::Command.new('list-machines', '-l') do |runner|
46
+ runner.run(host: host)
47
+ new_machines = parse_machines(runner.output)
48
+ if runner.exit_code == 0
49
+ return true
50
+ else
51
+ return false
52
+ end
53
+ end
54
+ end
55
+
56
+ def parse_machines(raw_table)
57
+ machine_hashes = Fleetctl::TableParser.parse(raw_table)
58
+ machine_hashes.map do |machine_attrs|
59
+ machine_attrs[:id] = machine_attrs.delete(:machine)
60
+ machine_attrs[:cluster] = self
61
+ add_or_find(Fleet::Machine.new(machine_attrs))
62
+ end
63
+ end
64
+
65
+ # attempts to rebuild the cluster by the specified fleet host, then hosts that it
66
+ # has built previously, and finally by using the discovery url
67
+ def discover!
68
+ known_hosts = [Fleetctl.options.fleet_host] | fleet_hosts.to_a
69
+ clear
70
+ success_host = build_from(known_hosts) || build_from(Fleet::Discovery.hosts)
71
+ if success_host
72
+ Fleetctl.logger.info 'Successfully recovered from host: ' + success_host.inspect
73
+ else
74
+ Fleetctl.logger.info 'Unable to recover!'
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,115 @@
1
+ module Fleet
2
+ class Controller
3
+ attr_writer :units
4
+ attr_accessor :cluster
5
+
6
+ def initialize
7
+ @cluster = Fleet::Cluster.new(controller: self)
8
+ end
9
+
10
+ # returns an array of Fleet::Machine instances
11
+ def machines
12
+ cluster.machines
13
+ end
14
+
15
+ # returns an array of Fleet::Unit instances
16
+ def units
17
+ return @units.to_a if @units
18
+ machines
19
+ fetch_units
20
+ @units.to_a
21
+ end
22
+
23
+ # refreshes local state to match the fleet cluster
24
+ def sync
25
+ build_fleet
26
+ fetch_units
27
+ true
28
+ end
29
+
30
+ # find a unitfile of a specific name
31
+ def [](unit_name)
32
+ units.detect { |u| u.name == unit_name }
33
+ end
34
+
35
+ # accepts one or more File objects, or an array of File objects
36
+ def start(*unit_file_or_files)
37
+ unitfiles = [*unit_file_or_files].flatten
38
+ out = unitfile_operation(:start, unitfiles)
39
+ clear_units
40
+ out
41
+ end
42
+
43
+ # accepts one or more File objects, or an array of File objects
44
+ def submit(*unit_file_or_files)
45
+ unitfiles = [*unit_file_or_files].flatten
46
+ out = unitfile_operation(:submit, unitfiles)
47
+ clear_units
48
+ out
49
+ end
50
+
51
+ # accepts one or more File objects, or an array of File objects
52
+ def load(*unit_file_or_files)
53
+ unitfiles = [*unit_file_or_files].flatten
54
+ out = unitfile_operation(:load, unitfiles)
55
+ clear_units
56
+ out
57
+ end
58
+
59
+ def destroy(*unit_names)
60
+ runner = Fleetctl::Command.run('destroy', unit_names)
61
+ clear_units
62
+ runner.exit_code == 0
63
+ end
64
+
65
+ private
66
+
67
+ def build_fleet
68
+ cluster.discover!
69
+ end
70
+
71
+ def fleet_host
72
+ cluster.fleet_host
73
+ end
74
+
75
+ def clear_units
76
+ @units = nil
77
+ end
78
+
79
+ def unitfile_operation(command, files)
80
+ clear_units
81
+ if Fleetctl.options.runner_class.to_s == 'Shell'
82
+ runner = Fleetctl::Command.run(command.to_s, files.map(&:path))
83
+ else
84
+ runner = nil
85
+ Fleetctl::RemoteTempfile.open(*files) do |*remote_filenames|
86
+ runner = Fleetctl::Command.run(command.to_s, remote_filenames)
87
+ end
88
+ end
89
+ runner.exit_code == 0
90
+ end
91
+
92
+ def fetch_units(host: fleet_host)
93
+ Fleetctl.logger.info 'Fetching units from host: '+host.inspect
94
+ @units = Fleet::ItemSet.new
95
+ Fleetctl::Command.new('list-units', '-l') do |runner|
96
+ runner.run(host: host)
97
+ parse_units(runner.output)
98
+ end
99
+ @units.to_a
100
+ end
101
+
102
+ def parse_units(raw_table)
103
+ unit_hashes = Fleetctl::TableParser.parse(raw_table)
104
+ unit_hashes.each do |unit_attrs|
105
+ if unit_attrs[:machine]
106
+ machine_id, machine_ip = unit_attrs[:machine].split('/')
107
+ unit_attrs[:machine] = cluster.add_or_find(Fleet::Machine.new(id: machine_id, ip: machine_ip))
108
+ end
109
+ unit_attrs[:name] = unit_attrs.delete(:unit)
110
+ unit_attrs[:controller] = self
111
+ @units.add_or_find(Fleet::Unit.new(unit_attrs))
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,30 @@
1
+ module Fleet
2
+ class Discovery
3
+ class << self
4
+ def hosts
5
+ new(discovery_url).hosts
6
+ end
7
+ end
8
+
9
+ attr_accessor :discovery_url
10
+
11
+ def initialize(discovery_url = Fleetctl.options.discovery_url)
12
+ @discovery_url = discovery_url
13
+ end
14
+
15
+ def data
16
+ @data ||= JSON.parse(Net::HTTP.get(URI.parse(@discovery_url)))
17
+ end
18
+
19
+ def hosts
20
+ begin
21
+ data['node']['nodes'].map{|node| node['value'].split(':')[0..1].join(':').split('//').last}
22
+ rescue => e
23
+ Fleetctl.logger.error 'ERROR in Fleet::Discovery#hosts, returning empty set'
24
+ Fleetctl.logger.error e.message
25
+ Fleetctl.logger.error e.backtrace.join("\n")
26
+ []
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ module Fleet
2
+ class ItemSet < Set
3
+ def add_or_find(obj)
4
+ res = self.detect { |member| member == obj }
5
+ if res
6
+ res
7
+ else
8
+ add(obj)
9
+ obj
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module Fleet
2
+ class Machine
3
+ attr_reader :cluster, :id, :ip, :metadata
4
+
5
+ def initialize(cluster: nil, id: nil, ip: nil, metadata: nil)
6
+ @cluster = cluster
7
+ @id = id
8
+ @ip = ip
9
+ @metadata = metadata
10
+ end
11
+
12
+ def controller
13
+ cluster.controller
14
+ end
15
+
16
+ def units
17
+ controller.units.select { |unit| unit.machine.id == id }
18
+ end
19
+
20
+ # run the command (string, array of command + args, whatever) and return stdout
21
+ def ssh(*command, port: 22)
22
+ runner = Fleetctl::Runner::SSH.new([*command].flatten.compact.join(' '))
23
+ runner.run(host: ip, ssh_options: { port: port })
24
+ runner.output
25
+ end
26
+
27
+ def ==(other_machine)
28
+ id == other_machine.id && ip == other_machine.ip
29
+ end
30
+
31
+ alias_method :eql?, :==
32
+ end
33
+ end
data/lib/fleet/unit.rb ADDED
@@ -0,0 +1,81 @@
1
+ module Fleet
2
+ class Unit
3
+ # http://linuxrackers.com/doku.php?id=fedora_systemd_services
4
+ # LOAD = Reflects whether the unit definition was properly loaded.
5
+ # ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
6
+ # SUB = The low-level unit activation state, values depend on unit type.
7
+ attr_reader :controller, :name, :state, :load, :active, :sub, :desc, :machine
8
+
9
+ def initialize(controller:, name:, state:, load:, active:, sub:, desc:, machine:)
10
+ @controller = controller
11
+ @name = name
12
+ @state = state
13
+ @load = load
14
+ @active = active
15
+ @sub = sub
16
+ @machine = machine
17
+ end
18
+
19
+ [:status, :destroy, :stop, :start, :cat, :unload].each do |method_name|
20
+ define_method(method_name) do
21
+ cmd = Fleetctl::Command.new(method_name, self.name)
22
+ runner = cmd.run(host: ip)
23
+ runner.output
24
+ end
25
+ end
26
+
27
+ def ip
28
+ machine && machine.ip
29
+ end
30
+
31
+ def creating?
32
+ active == 'activating' && sub == 'start-pre'
33
+ end
34
+
35
+ def failed?
36
+ active == 'failed' && sub == 'failed'
37
+ end
38
+
39
+ def running?
40
+ active == 'active' && sub == 'running'
41
+ end
42
+
43
+ # run the command on host (string, array of command + args, whatever) and return stdout
44
+ def ssh(*command, port: 22)
45
+ runner = Fleetctl::Runner::SSH.new([*command].flatten.compact.join(' '))
46
+ runner.run(host: ip, ssh_options: { port: port })
47
+ runner.output
48
+ end
49
+
50
+ # gets the external port mapped to port 22 inside this unit's docker container
51
+ # assumes that this unit corresponds to a docker container
52
+ def container_ssh_port(container_name = name)
53
+ return @container_ssh_port if defined? @container_ssh_port
54
+ docker_runner = Fleetctl::Runner::SSH.new('docker', 'port', container_name, 22)
55
+ docker_runner.run(host: ip)
56
+ @container_ssh_port = docker_runner.output.split(':').last.rstrip
57
+ end
58
+
59
+ # TODO: GET THIS WORKING
60
+ # # attempts to execute a command via ssh directly on the container
61
+ # # assumes that this unit corresponds to a docker container
62
+ # def container_ssh(*command, container_name: name, key: Dir.home+'/.ssh/id_rsa', username: 'root', password: nil)
63
+ # cmd_runner = Fleetctl::Runner::SSH.new([*command].flatten.compact.join(' '))
64
+ # cmd_runner.run(host: ip, ssh_options: { port: container_ssh_port(container_name), keys: [*key], username: username, password: password})
65
+ # runner.output
66
+ # end
67
+
68
+ # returns a JSON object representing the container
69
+ # assumes that this unit corresponds to a docker container
70
+ def docker_inspect(container_name = name)
71
+ raw = ssh('docker', 'inspect', container_name)
72
+ JSON.parse(raw)
73
+ end
74
+
75
+ def ==(other_unit)
76
+ name == other_unit.name
77
+ end
78
+
79
+ alias_method :eql?, :==
80
+ end
81
+ end
@@ -0,0 +1,45 @@
1
+ module Fleetctl
2
+ class Command
3
+ attr_accessor :command
4
+
5
+ class << self
6
+ def run(*cmd, &blk)
7
+ obj = new(*cmd, &blk)
8
+ obj.run
9
+ end
10
+ end
11
+
12
+ def initialize(*cmd)
13
+ @command = cmd
14
+ yield(runner) if block_given?
15
+ end
16
+
17
+ def run(*args)
18
+ runner.run(*args)
19
+ runner
20
+ end
21
+
22
+ def runner
23
+ klass = "Fleetctl::Runner::#{Fleetctl.options.runner_class}".constantize
24
+ @runner ||= klass.new(expression)
25
+ end
26
+
27
+ private
28
+
29
+ def global_options
30
+ Fleetctl.options.global.map { |k,v| "--#{k.to_s.gsub('_','-')}=#{v}" }
31
+ end
32
+
33
+ def prefix
34
+ Fleetctl.options.command_prefix
35
+ end
36
+
37
+ def executable
38
+ Fleetctl.options.executable
39
+ end
40
+
41
+ def expression
42
+ [prefix, executable, global_options, command].flatten.compact.join(' ')
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ module Fleetctl
2
+ class Options < Hashie::Mash
3
+ def initialize(*)
4
+ deep_merge!(Hashie::Mash.new(defaults))
5
+ super
6
+ end
7
+
8
+ def defaults
9
+ {
10
+ global: {},
11
+ executable: 'fleetctl',
12
+ logger: Logger.new(STDOUT),
13
+ runner_class: 'SSH',
14
+ command_prefix: nil,
15
+ discovery_url: nil,
16
+
17
+ # for use with runner_class: 'SSH'
18
+ # these aren't used wih a Shell runner
19
+ fleet_host: nil,
20
+ fleet_user: 'core',
21
+ ssh_options: {},
22
+ remote_temp_dir: '/tmp'
23
+ }
24
+ end
25
+
26
+ def ssh_options
27
+ self[:ssh_options].symbolize_keys
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Fleetctl
2
+ class RemoteTempfile
3
+ class << self
4
+ def open(local_file)
5
+ remote_path = File.join(Fleetctl.options.remote_temp_dir, File.basename(local_file.path))
6
+ Net::SCP.upload!(Fleetctl.options.fleet_host, Fleetctl.options.fleet_user, local_file.path, remote_path, :ssh => Fleetctl.options.ssh_options)
7
+ yield(remote_path)
8
+ Net::SSH.start(Fleetctl.options.fleet_host, Fleetctl.options.fleet_user, Fleetctl.options.ssh_options) { |ssh| ssh.exec!("rm #{remote_path}") }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module Fleetctl
2
+ module Runner
3
+ class Runner
4
+ attr_reader :command, :status, :exit_code, :stdout_data, :stderr_data, :exit_signal
5
+
6
+ def initialize(*command)
7
+ @command = [*command].flatten.compact.join(' ')
8
+ end
9
+
10
+ def run(*)
11
+ fail NotImplementedError
12
+ end
13
+
14
+ def output
15
+ @output || run
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Fleetctl
2
+ module Runner
3
+ class Shell < ::Fleetctl::Runner::Runner
4
+ def run(*)
5
+ return @output if @output
6
+ Fleetctl.logger.info "#{self.class.name} RUNNING: #{command}"
7
+ @stdout_data = `#{command}`
8
+ @status = $?
9
+
10
+ @exit_signal = @status.termsig
11
+ @exit_code = @status.exitstatus
12
+ Fleetctl.logger.info "EXIT CODE!: #{@exit_code.inspect}"
13
+ Fleetctl.logger.info "STDOUT: #{@output.inspect}"
14
+ @output = @stdout_data
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ module Fleetctl
2
+ module Runner
3
+ class SSH < ::Fleetctl::Runner::Runner
4
+ def run(host: Fleetctl.options.fleet_host, user: Fleetctl.options.fleet_user, ssh_options: {})
5
+ begin
6
+ ssh_options = Fleetctl.options.ssh_options.merge(ssh_options)
7
+ # return @output if @output
8
+ Fleetctl.logger.info "#{self.class.name} #{user}@#{host} RUNNING: #{command.inspect}"
9
+ Net::SSH.start(host, user, ssh_options) do |ssh|
10
+ @stdout_data = ''
11
+ @stderr_data = ''
12
+ @exit_code = nil
13
+ @exit_signal = nil
14
+ ssh.open_channel do |channel|
15
+ channel.exec(command) do |ch, success|
16
+ unless success
17
+ abort "FAILED: couldn't execute command (ssh.channel.exec)"
18
+ end
19
+ channel.on_data do |ch,data|
20
+ @stdout_data+=data
21
+ end
22
+
23
+ channel.on_extended_data do |ch,type,data|
24
+ @stderr_data+=data
25
+ end
26
+
27
+ channel.on_request('exit-status') do |ch,data|
28
+ @exit_code = data.read_long
29
+ end
30
+
31
+ channel.on_request('exit-signal') do |ch, data|
32
+ @exit_signal = data.read_long
33
+ end
34
+ end
35
+ end
36
+ ssh.loop
37
+ @output = @stdout_data
38
+ end
39
+ Fleetctl.logger.info "EXIT CODE!: #{exit_code.inspect}"
40
+ Fleetctl.logger.info "STDOUT: #{@output.inspect}"
41
+ @output
42
+ rescue => e
43
+ Fleetctl.logger.error 'ERROR in Runner#run'
44
+ Fleetctl.logger.error e.message
45
+ Fleetctl.logger.error e.backtrace.join("\n")
46
+ raise e
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ module Fleetctl
2
+ class TableParser
3
+ attr_accessor :raw
4
+
5
+ class << self
6
+ def parse(raw)
7
+ self.new(raw).parse
8
+ end
9
+ end
10
+
11
+ def initialize(raw)
12
+ @raw = raw
13
+ end
14
+
15
+ def parse
16
+ rows = raw.split("\n").map { |row| row.split(/\t+/) }
17
+ header = rows.shift
18
+ if header
19
+ keys = header.map { |key| key.downcase.to_sym }
20
+ [].tap do |output|
21
+ rows.each do |row|
22
+ scrubbed_row = row.map { |val| val == '-' ? nil : val }
23
+ output << Hash[keys.zip(scrubbed_row)]
24
+ end
25
+ end
26
+ else
27
+ Fleetctl.logger.error('ERROR in Fleetctl::TableParser.parse - no header row found')
28
+ []
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Fleetctl
2
+ VERSION = '0.1.1'
3
+ end
data/lib/fleetctl.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'net/ssh'
2
+ require 'net/scp'
3
+ require 'hashie'
4
+
5
+ require 'fleetctl/version'
6
+ require 'fleetctl/command'
7
+ require 'fleetctl/runner/runner'
8
+ require 'fleetctl/runner/ssh'
9
+ require 'fleetctl/runner/shell'
10
+ require 'fleetctl/table_parser'
11
+ require 'fleetctl/options'
12
+ require 'fleetctl/remote_tempfile'
13
+
14
+ require 'fleet/item_set'
15
+ require 'fleet/unit'
16
+ require 'fleet/machine'
17
+ require 'fleet/controller'
18
+ require 'fleet/discovery'
19
+ require 'fleet/cluster'
20
+
21
+ module Fleetctl
22
+ class << self
23
+ extend Forwardable
24
+ def_delegators :instance, :machines, :units, :[], :sync, :start, :submit, :load, :destroy
25
+
26
+ attr_reader :options
27
+
28
+ # use if you might need more than one fleet
29
+ def new(*args)
30
+ Fleet::Controller.new(*args)
31
+ end
32
+
33
+ # get the global singleton controller
34
+ def instance
35
+ @instance ||= Fleet::Controller.new
36
+ end
37
+
38
+ # set global configuration options
39
+ def config(cfg)
40
+ @options = Options.new(cfg)
41
+ end
42
+
43
+ # set the logger for fleet to use
44
+ def logger
45
+ options.logger
46
+ end
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fleetctl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Josh Lauer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hashie
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 3.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-ssh
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.9.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.9.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-scp
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.2.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.1
83
+ description: Allows controlling fleet clusters via a ruby API
84
+ email:
85
+ - jlauer@cloudspace.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - fleetctl.gemspec
96
+ - lib/fleet/cluster.rb
97
+ - lib/fleet/controller.rb
98
+ - lib/fleet/discovery.rb
99
+ - lib/fleet/item_set.rb
100
+ - lib/fleet/machine.rb
101
+ - lib/fleet/unit.rb
102
+ - lib/fleetctl.rb
103
+ - lib/fleetctl/command.rb
104
+ - lib/fleetctl/options.rb
105
+ - lib/fleetctl/remote_tempfile.rb
106
+ - lib/fleetctl/runner/runner.rb
107
+ - lib/fleetctl/runner/shell.rb
108
+ - lib/fleetctl/runner/ssh.rb
109
+ - lib/fleetctl/table_parser.rb
110
+ - lib/fleetctl/version.rb
111
+ homepage: https://github.com/josh-lauer/ruby-fleetctl
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.2.2
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: A simple wrapper for fleetctl
135
+ test_files: []