wfa 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+ gem 'debugger', group:['development', 'test']
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ wfa(1) -- manage workfrom devices
2
+ =================================
3
+
4
+ ## SYNOPSIS
5
+
6
+ wfa [-a|--show-all] [list]
7
+ wfa [-D|--dry-run] [shell] @<device-tag>
8
+ wfa [-D|--dry-run] [run] @<device-tag> <remote-command ...>
9
+ wfa identify <device-id> @<device-tag> <name>
10
+ wfa screen @<device-tag>
11
+ wfa heartbeats @<device-tag>
12
+ wfa console
13
+
14
+ ## DESCRIPTION
15
+
16
+ `wfa` is a tool for listing and connecting to workfrom devices via SSH.
17
+ It is operated using sub-commands, but for simple use cases the sub-command is assumed.
18
+
19
+ ## COMMANDS
20
+
21
+ ### list
22
+
23
+ Display a list of registered devices.
24
+ Each device has a tag assigned for use in issuing commands.
25
+ This command is assumed if you provide no arguments.
26
+ Use `-a` to list untagged devices.
27
+
28
+ ### shell
29
+
30
+ Open an interactive shell on the remote device, specified by the device tag.
31
+ This command is assumed if you provide a device tag without additional arguments.
32
+
33
+ ### run
34
+
35
+ Issue a command on a device and display the output.
36
+ This command is assumed if you provide a device tag with an additional command argument.
37
+
38
+ ### identify
39
+
40
+ Set a device tag and name, given the device ID.
41
+ Devices must be tagged in order to be maintained with wfa.
42
+
43
+ ### screen
44
+
45
+ Open a screen window to a shell on the remote device, specified by the device tag.
46
+ Useful if you're running screen and want titled windows for each device.
47
+
48
+ ### heartbeats
49
+
50
+ List the last 100 or so device heartbeats, specified by the device tag.
51
+
52
+ ### console
53
+
54
+ Open an interactive Ruby prompt with a ready API handle.
55
+ It's useful for experimentation or exploring the API.
56
+
57
+ ## USAGE
58
+
59
+ **Note** that you must be logged in to the tunnel server (as yourself) to use the
60
+ SSH tunneling features.
61
+ No authentication is performed, you must have SSH auth already configured to use it.
62
+
63
+ To see a list of devices:
64
+
65
+ wfa
66
+
67
+ To open a shell on a device:
68
+
69
+ wfa @ovation
70
+
71
+ To run a command on a device:
72
+
73
+ wfa @ovation cat /etc/hostname
74
+
75
+ To run these commands from your own machine, prepend
76
+ `ssh -A <login>@staging.workfrom.co` and they'll work more or less the same way.
77
+ (`-A` enables agent forwarding, which you need to access the devices themselves.)
78
+
79
+ ## ADVANCED
80
+
81
+ ### Installation notes
82
+
83
+ To install the gem you need to install some library dependencies.
84
+ For example, this works on Debian:
85
+
86
+ sudo apt-get install ruby-dev libcurl4-openssl-dev
87
+
88
+ ### Distribution
89
+
90
+ Currently wfa is distributed via Zack's dropbox. The URL is:
91
+
92
+ https://dl.dropboxusercontent.com/u/16760254/wfa-latest.gem
93
+
94
+ To release a new version, Zack just builds the gem and copies to his dropbox public folder.
95
+
96
+ ## FILES
97
+
98
+ ### ~/.wfa.json
99
+
100
+ Saved settings for `wfa`, including login credentials and backend URL.
101
+
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems/tasks'
2
+
3
+ # I am dumb and keep forgetting to update the bundle before releasing
4
+ task :update_bundle do
5
+ system("bundle")
6
+ system("git ci -am 'update gemfile.lock'")
7
+ end
8
+ task :release => :update_bundle
9
+
10
+ require 'fileutils'
11
+ task :clean do
12
+ FileUtils.rm_rf %w[ pkg man ]
13
+ end
14
+
15
+ # process the README into a manual page using ronn
16
+ require 'ronn'
17
+ task :man do
18
+ print "Writing manual page..."
19
+ FileUtils.mkdir_p 'man'
20
+ File.open('man/wfa.1','w') do |man|
21
+ man.write Ronn::Document.new('README.md').to_roff
22
+ end
23
+ puts "done."
24
+ end
25
+ task "build:gem:wfa" => :man
26
+
27
+ Gem::Tasks.new
28
+ task :default => "build:gem:wfa"
data/bin/wfa ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'wfa'
3
+ WFA::Command.new(ARGV).start_wfa_command
@@ -0,0 +1,203 @@
1
+ require 'optparse'
2
+ require 'io/console'
3
+ require 'yajl'
4
+ require 'fileutils'
5
+
6
+ require 'wfa/generic_api'
7
+
8
+ class WFA::Command
9
+ def initialize args
10
+ @device_tag, @before_args, @after_args = extract_device_tag args
11
+ option_parser.permute!(@before_args)
12
+
13
+ @settings = {}
14
+ load_settings unless no_cache
15
+
16
+ @command =
17
+ if @device_tag.nil?
18
+ @before_args.shift || 'list'
19
+ else
20
+ if @before_args.count > 0
21
+ @before_args.shift
22
+ elsif @after_args.count > 0
23
+ "run"
24
+ else
25
+ "shell"
26
+ end
27
+ end
28
+ end
29
+
30
+ def start_wfa_command
31
+ case @command
32
+ when 'identify'
33
+ public_send @command, *@before_args, @device_tag, *@after_args
34
+ else
35
+ public_send @command, @device_tag, *@after_args
36
+ end
37
+ save_settings
38
+ rescue NoMethodError => e
39
+ $stderr.puts "no such command: #{@command}"
40
+ rescue Faraday::ClientError => e
41
+ $stderr.puts e.message
42
+ rescue StandardError => e
43
+ $stderr.puts e.message
44
+ end
45
+
46
+ def console *args
47
+ puts "Try e.g.: http.get('devices').body"
48
+ WFA::Console.new self, net
49
+ end
50
+
51
+ # list configured devices
52
+ def list *args
53
+ # force a reload of the device list
54
+ WFA::Device.fetch_and_cache_device_list(net)
55
+ puts WFA::Device.all(net).select {|device| show_all || device.tag }
56
+ end
57
+
58
+ # open a screen on the specified device
59
+ def screen device_tag, *args
60
+ device = WFA::Device.by_tag(net, device_tag) or raise "no such device"
61
+ shell_command "screen -t #{device.tag} -- ssh -A workfrom@#{device.last_ip_addr}"
62
+ end
63
+
64
+ # open a shell on the specified device
65
+ def shell device_tag, *args
66
+ device = WFA::Device.by_tag(net, device_tag) or raise "no such device"
67
+ shell_command "ssh -A workfrom@#{device.last_ip_addr}"
68
+ end
69
+
70
+ # issue a command on the specified device
71
+ def run device_tag, *args
72
+ device = WFA::Device.by_tag(net, device_tag) or raise "no such device"
73
+ shell_command "ssh -A workfrom@#{device.last_ip_addr} #{args.join(' ')}"
74
+ end
75
+
76
+ def heartbeats device_tag, *args
77
+ device = WFA::Device.by_tag(net, device_tag) or raise "no such device"
78
+ puts device.heartbeats(net)
79
+ end
80
+
81
+ def identify port, device_tag, *args
82
+ device = WFA::Device.find(net, id)
83
+ if device.update_remote(net, tag:device_tag.sub(/^@/,''), name:args.join(' '))
84
+ puts 'updated!'
85
+ else
86
+ puts 'update failed :('
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ attr_accessor :dry_run, :base_url_option, :no_cache, :show_all, :login_option, :password_option
93
+
94
+ def load_settings
95
+ @settings = Yajl::Parser.parse(File.read("#{ENV['HOME']}/.wfa.json"), symbolize_keys:true)
96
+ rescue Errno::ENOENT, Yajl::ParseError
97
+ end
98
+
99
+ def save_settings
100
+ File.open("#{ENV['HOME']}/.wfa.json", "w").tap do |f|
101
+ f.write(Yajl::Encoder.encode(@settings))
102
+ f.close
103
+ FileUtils.chmod(0600, "#{ENV['HOME']}/.wfa.json")
104
+ end
105
+ end
106
+
107
+ def login_setting
108
+ @settings[:login] ||= ask("backend login: ")
109
+ end
110
+
111
+ def password_setting
112
+ @settings[:password] ||= ask_private("backend password: ")
113
+ end
114
+
115
+ def base_url_setting
116
+ @settings[:base_url] ||= ask("backend URL: ")
117
+ end
118
+
119
+ def ask prompt
120
+ print prompt
121
+ $stdin.readline.chomp
122
+ end
123
+
124
+ def ask_private prompt
125
+ print prompt
126
+ $stdin.noecho(&:readline).chomp.tap { puts }
127
+ end
128
+
129
+
130
+ def net_without_auth
131
+ GenericApi.connect(base_url_option||base_url_setting)
132
+ end
133
+
134
+ def net
135
+ @net ||= begin
136
+ login = login_option||login_setting
137
+ password = password_option||password_setting
138
+ url = base_url_option||base_url_setting
139
+ GenericApi.connect(url, login, password)
140
+ end
141
+ end
142
+
143
+ # execute a shell command. in dry run mode, just print the command to be executed.
144
+ def shell_command cmd
145
+ if dry_run
146
+ puts cmd
147
+ else
148
+ exec cmd
149
+ end
150
+ end
151
+
152
+ # configure and return the option parser
153
+ def option_parser
154
+ self.base_url_option = ENV['WFDEVICE_BACKEND_URL']
155
+ self.login_option = nil
156
+ self.password_option = nil
157
+ self.no_cache = false
158
+ self.dry_run = false
159
+ self.show_all = false
160
+ OptionParser.new("Usage: wfa [-aCD] [<command>] [@<device_tag>] ...") do |opts|
161
+ opts.on("-u URL", "--base-url URL", "backend base URL") do |url|
162
+ self.base_url_option = url
163
+ end
164
+ opts.on("-l NAME", "--login NAME", "backend login name") do |name|
165
+ self.login_option = name
166
+ end
167
+ opts.on("-p PASS", "--p PASS", "backend password") do |pass|
168
+ self.password_option = pass
169
+ end
170
+ opts.on("-a", "--show-all", "Show untagged devices in listing") do
171
+ self.show_all = true
172
+ end
173
+ opts.on("-C", "--clear-settings", "Clear local settings before the operation") do
174
+ self.no_cache = true
175
+ end
176
+ opts.on("-D", "--dry-run", "Print the command that would be run without running it") do
177
+ self.dry_run = true
178
+ end
179
+ end
180
+ end
181
+
182
+ # given the complete command arguments, find the device tag (if any)
183
+ # and return arrays of arguments both before and after the tag.
184
+ # If no tag is present all args are in the returned before args.
185
+ def extract_device_tag args
186
+ args = args.dup
187
+ tag = nil
188
+ before = []
189
+ after = []
190
+ args.each do |a|
191
+ if tag.nil?
192
+ if a.match(/^@[\w-]+$/)
193
+ tag = a
194
+ else
195
+ before << a
196
+ end
197
+ else
198
+ after << a
199
+ end
200
+ end
201
+ [tag, before, after]
202
+ end
203
+ end
@@ -0,0 +1,16 @@
1
+ require 'pp'
2
+ require 'pry'
3
+
4
+ module WFA
5
+ class Console
6
+ attr_reader :wfa, :http
7
+ def initialize app, http
8
+ @wfa = app
9
+ @http = http
10
+ prompt = $PROGRAM_NAME.split('/').last + "> "
11
+ binding.pry quiet: true,
12
+ prompt:[->(a,b,c){ prompt }],
13
+ print:->(io, *ps){ ps.each {|p|PP.pp p, io, 100 } }
14
+ end
15
+ end
16
+ end
data/lib/wfa/device.rb ADDED
@@ -0,0 +1,98 @@
1
+ require 'csv'
2
+ require 'fileutils'
3
+ require 'time'
4
+
5
+ class WFA::Device
6
+ WFDEVICE_PORT_FILE = '/etc/wfdevice_tunnel_port.sh'
7
+ FING_CSV_FILE = '/var/fing.csv'
8
+
9
+ ATTRIBUTES = [
10
+ :id, :name, :tag, :tunnel_port, :macaddr, :secret_key,
11
+ :state, :vendor, :hostname, :last_heartbeat, :last_ip_addr
12
+ ]
13
+ attr_accessor(*ATTRIBUTES)
14
+
15
+ def initialize attrs
16
+ update attrs
17
+ end
18
+
19
+ def to_s
20
+ "@%s: %s (%d) [%s] %s" % [
21
+ tag||"---", name, id, last_ip_addr,
22
+ last_heartbeat ? time_ago(Time.parse(last_heartbeat)) : ''
23
+ ]
24
+ end
25
+
26
+ def time_ago timestamp
27
+ seconds = (Time.now - timestamp).to_i
28
+ minutes = seconds / 60
29
+ return "#{seconds}s" unless minutes > 1
30
+ hours = minutes / 60
31
+ return "#{minutes}m" unless hours > 1
32
+ days = hours / 24
33
+ return "#{hours}h" unless days > 1
34
+ weeks = days / 7
35
+ return "#{days}d" unless weeks > 1
36
+ "#{weeks}w"
37
+ end
38
+
39
+ def heartbeats net
40
+ net.get(path("heartbeats")).body[:heartbeats].map{|h| WFA::Heartbeat.new(h) }
41
+ end
42
+
43
+ def update_remote net, attrs
44
+ update attrs
45
+ net.put(path, name:name, tag:tag).success?
46
+ end
47
+
48
+ def update attrs
49
+ ATTRIBUTES.each { |attr|
50
+ if !attrs[attr].nil? && send(attr) != attrs[attr]
51
+ send("#{attr}=", attrs[attr])
52
+ end
53
+ }
54
+ end
55
+
56
+ def path *args
57
+ (["devices/#{id}"]+args).join("/")
58
+ end
59
+
60
+ class << self
61
+ def device_list_cache
62
+ "#{ENV['HOME']}/.wfa_device_list.json"
63
+ end
64
+
65
+ def by_tag net, tag
66
+ tag = tag.sub(/^@/,'')
67
+ all(net).detect {|device| device.tag == tag }
68
+ end
69
+
70
+ def find net, id
71
+ all(net).detect {|device| device.id == id }
72
+ end
73
+
74
+ def all net
75
+ @all ||= devices_list(net)[:devices].map do |device|
76
+ new device
77
+ end
78
+ end
79
+
80
+ def devices_list net
81
+ get_cached_device_list || fetch_and_cache_device_list(net)
82
+ end
83
+
84
+ def get_cached_device_list
85
+ File.exists?(device_list_cache) \
86
+ ? Yajl::Parser.parse(File.read(device_list_cache), symbolize_keys:true) \
87
+ : raise('no cache!')
88
+ end
89
+
90
+ def fetch_and_cache_device_list net
91
+ net.get('devices').body.tap do |ds|
92
+ f = File.open(device_list_cache, "w")
93
+ f.write(Yajl::Encoder.encode(ds))
94
+ f.close
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,97 @@
1
+ #
2
+ # This module is a starter Ruby client for your REST API, based on Faraday:
3
+ # https://github.com/lostisland/faraday/
4
+ #
5
+ # It supports HTTP basic authentication, sending and recieving JSON, and simple error reporting.
6
+ # I use this as a basis for building API clients in most of my Ruby projects.
7
+ #
8
+ # Use it like this:
9
+ #
10
+ # http = GenericApi.connect('http://localhost:3000/', 'myname', 'secret')
11
+ # http.get('boxes.json').body.each do |box|
12
+ # puts "found a box named #{box[:name]}! Let's put something inside."
13
+ # http.post('boxes/#{box[:id]}.json', contents: 'something!')
14
+ # end
15
+ #
16
+ # A few hints for getting started:
17
+ #
18
+ # This uses Patron as the HTTP client but you can change those constants to use
19
+ # another adapter or set to nil to use the built-in default.
20
+ #
21
+ # The response body is a Hash with Symbol keys (not String!).
22
+ # If you're using ActiveSupport, you should use a hash with indifferent access instead.
23
+ #
24
+ # The default MIME type is appropriate for most purposes, but some APIs use it for
25
+ # versioning so bear that in mind and change MIME_TYPE if necessary.
26
+ #
27
+ # Warmest regards,
28
+ #
29
+ # Zack Hobson <zack@zackhobson.com>
30
+ #
31
+ require 'faraday'
32
+ module GenericApi
33
+ FARADAY_ADAPTER = :patron
34
+ REQUIRE_DEPENDENCIES = ['patron']
35
+ MIME_TYPE = 'application/json'
36
+
37
+ # Connect and return a `Faraday::Connection` instance:
38
+ # http://rdoc.info/github/lostisland/faraday/Faraday/Connection
39
+ #
40
+ # If login and password are provided, the connection will use basic auth.
41
+ def self.connect url, login=nil, password=nil
42
+ Faraday.new(url) do |f|
43
+ f.use :generic_api, login, password
44
+ f.adapter(FARADAY_ADAPTER || Faraday.default_adapter)
45
+ end
46
+ end
47
+
48
+ class Middleware < Faraday::Request::BasicAuthentication
49
+ Faraday::Middleware.register_middleware generic_api: ->{ self }
50
+
51
+ dependency do
52
+ require 'yajl'
53
+ (REQUIRE_DEPENDENCIES||[]).each {|dep| require dep }
54
+ end
55
+
56
+ def call(env)
57
+ # encode with and accept json
58
+ env[:request_headers]['Accept'] = MIME_TYPE
59
+ env[:request_headers]['Content-Type'] = MIME_TYPE
60
+ env[:body] = Yajl::Encoder.encode(env[:body]) if env[:body]
61
+
62
+ # response processing
63
+ super(env).on_complete do |env|
64
+ begin
65
+ env[:body] = Yajl::Parser.parse(env[:body], symbolize_keys:true)
66
+ # XXX or, if you're using ActiveSupport:
67
+ # env[:body] = Yajl::Parser.parse(env[:body]).with_indifferent_access
68
+ rescue Yajl::ParseError
69
+ raise ApiError.new("Unable to parse the response:\n#{env[:body]}", env)
70
+ end
71
+ case env[:status]
72
+ when 300..399
73
+ raise RedirectError.new(env[:body][:message], env)
74
+ when 400..499
75
+ raise AuthError.new(env[:body][:message], env)
76
+ when 500..599
77
+ raise ApiError.new(env[:body][:message], env)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ class ApiError < Faraday::ClientError
83
+ def initialize(message, response)
84
+ super("Upstream API failure: #{message||'Internal error.'}", response)
85
+ end
86
+ end
87
+ class AuthError < Faraday::ClientError
88
+ def initialize(message, response)
89
+ super("Authentication failure: #{message||'No reason given.'}", response)
90
+ end
91
+ end
92
+ class RedirectError < Faraday::ClientError
93
+ def initialize(message, response)
94
+ super("Redirected: #{message||'No reason given.'}", response)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,14 @@
1
+ require 'time'
2
+
3
+ class WFA::Heartbeat
4
+ attr_reader :active_nodes, :created_at, :ip_addr
5
+ def initialize attrs
6
+ @ip_addr = attrs[:ip_addr]
7
+ @active_nodes = attrs[:active_nodes]
8
+ @created_at = Time.parse(attrs[:created_at])
9
+ end
10
+
11
+ def to_s
12
+ "%s: %d active nodes [%s]" % [ created_at.strftime("%F %T"), active_nodes, ip_addr ]
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module WFA
2
+ VERSION = "0.1"
3
+ end
data/lib/wfa.rb ADDED
@@ -0,0 +1,8 @@
1
+ # must be required at start to register itself as a middleware
2
+ module WFA
3
+ autoload :VERSION, 'wfa/version'
4
+ autoload :Command, 'wfa/command'
5
+ autoload :Console, 'wfa/console'
6
+ autoload :Device, 'wfa/device'
7
+ autoload :Heartbeat, 'wfa/heartbeat'
8
+ end
data/man/wfa.1 ADDED
@@ -0,0 +1,121 @@
1
+ .\" generated with Ronn/v0.7.3
2
+ .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
+ .
4
+ .TH "WFA" "1" "June 2014" "" ""
5
+ .
6
+ .SH "NAME"
7
+ \fBwfa\fR \- manage workfrom devices
8
+ .
9
+ .SH "SYNOPSIS"
10
+ .
11
+ .nf
12
+
13
+ wfa [\-a|\-\-show\-all] [list]
14
+ wfa [\-D|\-\-dry\-run] [shell] @<device\-tag>
15
+ wfa [\-D|\-\-dry\-run] [run] @<device\-tag> <remote\-command \.\.\.>
16
+ wfa identify <device\-id> @<device\-tag> <name>
17
+ wfa screen @<device\-tag>
18
+ wfa heartbeats @<device\-tag>
19
+ wfa console
20
+ .
21
+ .fi
22
+ .
23
+ .SH "DESCRIPTION"
24
+ \fBwfa\fR is a tool for listing and connecting to workfrom devices via SSH\. It is operated using sub\-commands, but for simple use cases the sub\-command is assumed\.
25
+ .
26
+ .SH "COMMANDS"
27
+ .
28
+ .SS "list"
29
+ Display a list of registered devices\. Each device has a tag assigned for use in issuing commands\. This command is assumed if you provide no arguments\. Use \fB\-a\fR to list untagged devices\.
30
+ .
31
+ .SS "shell"
32
+ Open an interactive shell on the remote device, specified by the device tag\. This command is assumed if you provide a device tag without additional arguments\.
33
+ .
34
+ .SS "run"
35
+ Issue a command on a device and display the output\. This command is assumed if you provide a device tag with an additional command argument\.
36
+ .
37
+ .SS "identify"
38
+ Set a device tag and name, given the device ID\. Devices must be tagged in order to be maintained with wfa\.
39
+ .
40
+ .SS "screen"
41
+ Open a screen window to a shell on the remote device, specified by the device tag\. Useful if you\'re running screen and want titled windows for each device\.
42
+ .
43
+ .SS "heartbeats"
44
+ List the last 100 or so device heartbeats, specified by the device tag\.
45
+ .
46
+ .SS "console"
47
+ Open an interactive Ruby prompt with a ready API handle\. It\'s useful for experimentation or exploring the API\.
48
+ .
49
+ .SH "USAGE"
50
+ \fBNote\fR that you must be logged in to the tunnel server (as yourself) to use the SSH tunneling features\. No authentication is performed, you must have SSH auth already configured to use it\.
51
+ .
52
+ .P
53
+ To see a list of devices:
54
+ .
55
+ .IP "" 4
56
+ .
57
+ .nf
58
+
59
+ wfa
60
+ .
61
+ .fi
62
+ .
63
+ .IP "" 0
64
+ .
65
+ .P
66
+ To open a shell on a device:
67
+ .
68
+ .IP "" 4
69
+ .
70
+ .nf
71
+
72
+ wfa @ovation
73
+ .
74
+ .fi
75
+ .
76
+ .IP "" 0
77
+ .
78
+ .P
79
+ To run a command on a device:
80
+ .
81
+ .IP "" 4
82
+ .
83
+ .nf
84
+
85
+ wfa @ovation cat /etc/hostname
86
+ .
87
+ .fi
88
+ .
89
+ .IP "" 0
90
+ .
91
+ .P
92
+ To run these commands from your own machine, prepend \fBssh \-A <login>@staging\.workfrom\.co\fR and they\'ll work more or less the same way\. (\fB\-A\fR enables agent forwarding, which you need to access the devices themselves\.)
93
+ .
94
+ .SH "ADVANCED"
95
+ .
96
+ .SS "Installation notes"
97
+ To install the gem you need to install some library dependencies\. For example, this works on Debian:
98
+ .
99
+ .IP "" 4
100
+ .
101
+ .nf
102
+
103
+ sudo apt\-get install ruby\-dev libcurl4\-openssl\-dev
104
+ .
105
+ .fi
106
+ .
107
+ .IP "" 0
108
+ .
109
+ .SS "Distribution"
110
+ Currently wfa is distributed via Zack\'s dropbox\. The URL is:
111
+ .
112
+ .P
113
+ https://dl\.dropboxusercontent\.com/u/16760254/wfa\-latest\.gem
114
+ .
115
+ .P
116
+ To release a new version, Zack just builds the gem and copies to his dropbox public folder\.
117
+ .
118
+ .SH "FILES"
119
+ .
120
+ .SS "~/\.wfa\.json"
121
+ Saved settings for \fBwfa\fR, including login credentials and backend URL\.
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wfa
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Zack Hobson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yajl-ruby
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: patron
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: faraday
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: pry
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: ronn
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubygems-tasks
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Workfrom Admin tools
127
+ email: zack@zackhobson.com
128
+ executables:
129
+ - wfa
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - Rakefile
134
+ - Gemfile
135
+ - bin/wfa
136
+ - man/wfa.1
137
+ - README.md
138
+ - lib/wfa/command.rb
139
+ - lib/wfa/console.rb
140
+ - lib/wfa/device.rb
141
+ - lib/wfa/generic_api.rb
142
+ - lib/wfa/heartbeat.rb
143
+ - lib/wfa/version.rb
144
+ - lib/wfa.rb
145
+ homepage: http://workfrom.co/
146
+ licenses:
147
+ - private
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 1.8.23
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: command-line tool for listing and connecting to workfrom devices via SSH
170
+ test_files: []