appforce-spawn 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.appforce.example +4 -0
  3. data/.gitignore +8 -0
  4. data/Gemfile +17 -0
  5. data/History +85 -0
  6. data/README.md +144 -0
  7. data/ansible/README.md +31 -0
  8. data/ansible/common.yml +6 -0
  9. data/ansible/roles/common/tasks/.keep +0 -0
  10. data/ansible/roles/common/tasks/active_users.yml +23 -0
  11. data/ansible/roles/common/tasks/groups.yml +28 -0
  12. data/ansible/roles/common/tasks/inactive_users.yml +26 -0
  13. data/ansible/roles/common/tasks/main.yml +5 -0
  14. data/ansible/roles/common/tasks/setup.yml +6 -0
  15. data/ansible/roles/scout/tasks/.keep +0 -0
  16. data/ansible/roles/scout/tasks/install.yml +28 -0
  17. data/ansible/roles/scout/tasks/main.yml +2 -0
  18. data/ansible/roles/scout/vars/.keep +0 -0
  19. data/ansible/rvm.yml +13 -0
  20. data/ansible/scout.yml +6 -0
  21. data/ansible/site.yml +4 -0
  22. data/appforce-spawn.gemspec +24 -0
  23. data/bin/appforce-spawn +161 -0
  24. data/lib/appforce-spawn.rb +1 -0
  25. data/lib/appforce/config.rb +50 -0
  26. data/lib/appforce/logger.rb +25 -0
  27. data/lib/appforce/spawn.rb +312 -0
  28. data/lib/appforce/spawn/api.rb +4 -0
  29. data/lib/appforce/spawn/api/call.rb +217 -0
  30. data/lib/appforce/spawn/exceptions.rb +30 -0
  31. data/lib/appforce/spawn/runner.rb +143 -0
  32. data/lib/appforce/spawn/template.rb +102 -0
  33. data/lib/appforce/spawn/version.rb +10 -0
  34. data/spec/api_call_spec.rb +380 -0
  35. data/spec/config_spec.rb +51 -0
  36. data/spec/fixtures/all_host_data.json +12 -0
  37. data/spec/fixtures/appforce_config.yml +4 -0
  38. data/spec/fixtures/fake_private_key.yml +2 -0
  39. data/spec/fixtures/host_scout_vars.yml +10 -0
  40. data/spec/fixtures/hosts +8 -0
  41. data/spec/fixtures/inactive_users.yml +3 -0
  42. data/spec/fixtures/malformed_appforce_config.yml +4 -0
  43. data/spec/fixtures/private_key_vars.yml +4 -0
  44. data/spec/fixtures/scout_main.yml +2 -0
  45. data/spec/fixtures/users.yml +6 -0
  46. data/spec/fixtures/vars.yml +4 -0
  47. data/spec/logger_spec.rb +85 -0
  48. data/spec/runner_spec.rb +308 -0
  49. data/spec/spec_helper.rb +53 -0
  50. data/spec/template_spec.rb +160 -0
  51. data/spec/version_spec.rb +9 -0
  52. data/tmp/.keep +0 -0
  53. metadata +151 -0
File without changes
@@ -0,0 +1,13 @@
1
+ ---
2
+ - hosts: rvm
3
+ remote_user: "{{ ansible_user }}"
4
+ sudo: yes
5
+ roles:
6
+ - role: rvm_io.rvm1-ruby
7
+ sudo: yes
8
+ rvm1_user: 'synctree'
9
+ rvm1_install_path: '/home/synctree/.rvm'
10
+ rvm1_rvm_version: 'stable'
11
+ rvm1_install_flags: '--user-install'
12
+ rvm1_rubies:
13
+ - 'ruby-2.2.0'
@@ -0,0 +1,6 @@
1
+ ---
2
+ - hosts: scout
3
+ remote_user: "{{ ansible_user }}"
4
+ sudo: yes
5
+ roles:
6
+ - scout
@@ -0,0 +1,4 @@
1
+ ---
2
+ - include: common.yml
3
+ - include: rvm.yml
4
+ - include: scout.yml
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'appforce/spawn/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'appforce-spawn'
6
+ s.version = Appforce::Spawn::VERSION
7
+ s.date = Time.now.strftime('%Y-%m-%d')
8
+ s.summary = "Synctree user installation tool"
9
+ s.description = "A gem that uses the Appforce SSH key manager API to install users on client machines."
10
+ s.authors = ["Derek Smith"]
11
+ s.email = 'derek@synctree.com'
12
+ s.homepage = 'http://synctree.com'
13
+
14
+ s.files = `git ls-files`.split("\n").map {|f| f !~ /vagrant|test/ ? f : next }.compact
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib", "ansible"]
17
+
18
+ s.required_ruby_version = '>= 1.9.3'
19
+ s.add_dependency 'httparty', '~> 0.10', '>= 0.10.2'
20
+ s.add_dependency 'highline', '~> 1.7', '>= 1.7'
21
+ s.add_development_dependency "rspec", '~> 3.0'
22
+
23
+ s.license = 'Private'
24
+ end
@@ -0,0 +1,161 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "optparse"
4
+
5
+ $:.unshift(File.join(File.dirname(__FILE__), "/../lib"))
6
+ require "appforce-spawn"
7
+
8
+ opts = {
9
+ verbose: false
10
+ }
11
+
12
+ OptionParser.new do |o|
13
+ o.banner = "VERSION: #{Appforce::Spawn::VERSION}\nUSAGE: #{$0} [options]"
14
+
15
+ o.on("-a",
16
+ "--action [ACTION]",
17
+ "Action to perform. Use '-l' to view all available actions") do |a|
18
+ opts[:action] = a.downcase
19
+ end
20
+
21
+ o.on("-l",
22
+ "--list-actions",
23
+ "List available Actions") do |l|
24
+ opts[:list_actions] = true
25
+ end
26
+
27
+ o.on("-C",
28
+ "--client [CLIENT API NAME]",
29
+ "API Client Name to use for calls") do |cl|
30
+ opts[:client_api_name] = cl.downcase
31
+ end
32
+
33
+ o.on("-c", "--config [PATH]", "Optional custom config file") do |c|
34
+ opts[:config] = c
35
+ end
36
+
37
+ o.on("-D",
38
+ "--dump-config",
39
+ "Generate a example config template (to ~/.appforce.example)") do |d|
40
+ opts[:dump_config] = true
41
+ end
42
+
43
+ o.on("-v", "--verbose", "If set, print verbose output") do |v|
44
+ opts[:verbose] = true
45
+ end
46
+
47
+ o.on("--version", "Show current version and latest version available on RubyGems") do |v|
48
+ opts[:list_version] = true
49
+ end
50
+
51
+ o.on("-h", "--help", "Show help documentation") do |h|
52
+ STDERR.puts o
53
+ exit
54
+ end
55
+
56
+ end.parse!
57
+
58
+ if opts[:dump_config]
59
+ STDERR.puts " Dumping example config temlplate..."
60
+ Appforce::Spawn.dump_example_config
61
+ STDERR.puts " File written to ~/.appforce.example"
62
+ STDERR.puts " You will need to replace the API token and move the file to ~/.appforce"
63
+ exit
64
+ end
65
+
66
+ ##
67
+ # Settiung up logger
68
+ # TODO: Internalize this
69
+ logger = Logger.new(STDOUT)
70
+ opts[:verbose] ? logger.level = Logger::DEBUG : logger.level = Logger::INFO
71
+ logger.formatter = proc do |severity, datetime, progname, msg|
72
+ "[#{severity}] #{datetime.strftime("%Y-%m-%d %H:%M:%S")} #{msg}\n"
73
+ end
74
+ Appforce::Spawn.logger = logger
75
+ Appforce::Spawn.logger.debug "Debug logging is on."
76
+
77
+ ##
78
+ # List Version
79
+ if opts[:list_version]
80
+ installed = Appforce::Spawn::VERSION
81
+ latest = Appforce::Spawn.latest_version
82
+
83
+ STDERR.puts " appforce-spawn gem version: #{installed}"
84
+ STDERR.puts " latest version available: #{latest}"
85
+ if latest == installed
86
+ STDERR.puts " You are up-to-date"
87
+ else
88
+ STDERR.puts " A new version is available. Run the following to update:"
89
+ STDERR.puts " gem update appforce-spawn"
90
+ end
91
+ exit
92
+ end
93
+
94
+ ##
95
+ # List actions to screen
96
+ if opts[:list_actions]
97
+ STDERR.puts Appforce::Spawn.header("Avaialable Actions")
98
+ Appforce::Spawn.available_actions.each do |m|
99
+ STDERR.printf " %-20s # %s\n", m[0], m[1][:info]
100
+ end
101
+ STDERR.puts Appforce::Spawn.footer
102
+ exit
103
+ end
104
+
105
+ unless opts[:action]
106
+ STDERR.puts Appforce::Spawn.header("An Action Is Required")
107
+ STDERR.puts " USAGE: #{$0} [options]"
108
+ STDERR.puts " Use '-h' or '--help' to list all options."
109
+ STDERR.puts " Use '-l' or '--list-actions' to list all actions."
110
+ STDERR.puts Appforce::Spawn.footer
111
+ exit
112
+ end
113
+
114
+ unless Appforce::Spawn.available_actions.keys.include? opts[:action]
115
+ STDERR.puts Appforce::Spawn.header("Action '#{opts[:action]}' Not Supported")
116
+ STDERR.puts Appforce::Spawn.header("Avaialable Actions")
117
+ Appforce::Spawn.available_actions.each do |m|
118
+ STDERR.printf " %-20s # %s\n", m[0], m[1][:info]
119
+ end
120
+ STDERR.puts Appforce::Spawn.footer
121
+ exit
122
+ end
123
+
124
+ ##
125
+ # Do stuff down here
126
+ Appforce::Spawn.load_config(opts[:config])
127
+ if opts[:verbose]
128
+ logger.debug "Parameters as read from the config file:"
129
+ Appforce::Spawn.config.marshal_dump.keys.each {|k| logger.debug "-- #{k}: #{Appforce::Spawn.config.send(k)}"}
130
+ end
131
+
132
+ ##
133
+ # validate Client API Name before proceeding
134
+ if opts[:client_api_name]
135
+ resp = Appforce::Spawn.get_clients
136
+ a = []
137
+ resp.each do |item|
138
+ a.push item['api_name']
139
+ end
140
+ unless a.include? opts[:client_api_name]
141
+ logger.fatal "-- '#{opts[:client_api_name]}' is not a valid Client API name."
142
+ Appforce::Spawn.clients
143
+ exit
144
+ end
145
+ end
146
+
147
+ # Execute method
148
+ result = Appforce::Spawn.send(
149
+ Appforce::Spawn.available_actions[opts[:action]][:cmd],
150
+ ARGV.first,
151
+ opts)
152
+
153
+ if opts[:action] =~ /hosts|users|vars|^ping/
154
+ STDERR.puts Appforce::Spawn.header("The YAML output from the API will be written to STDOUT below")
155
+ STDERR.puts "\n"
156
+ puts "#{result}"
157
+ STDERR.puts "\n"
158
+ STDERR.puts STDERR.puts Appforce::Spawn.header("Action Complete")
159
+ else
160
+ logger.info "Action Complete."
161
+ end
@@ -0,0 +1 @@
1
+ require 'appforce/spawn'
@@ -0,0 +1,50 @@
1
+ module Appforce
2
+ class Config
3
+ require 'yaml'
4
+ require 'ostruct'
5
+
6
+ # read in config file
7
+ # default location is root at runtime
8
+ DEFAULT_CONFIG_PATH = "#{File.expand_path('~')}/.appforce"
9
+
10
+ def self.load_config(path=nil)
11
+ begin
12
+ temp = YAML::load(File.open(path.nil? ? DEFAULT_CONFIG_PATH : path))
13
+ @config = OpenStruct.new temp
14
+ rescue Exception => e
15
+ logger.fatal "[#{self.name}##{__method__.to_s}] Appforce::Spawn::ConfigFileParseError"\
16
+ " -- Config file appears to be malformed -- #{e}"
17
+ raise e
18
+ end
19
+ end
20
+
21
+ def self.config
22
+ @config
23
+ end
24
+
25
+ def self.dump_example_config
26
+ path = "#{File.expand_path('~')}/.appforce.example"
27
+ h = {
28
+ 'api_host' => 'https://afuka.synctree.com',
29
+ 'api_version' => 'api/v1',
30
+ 'api_token' => 'PUT_API_TOKEN_HERE'
31
+ }
32
+ begin
33
+ file = File.open(path, "w")
34
+ file.write(h.to_yaml)
35
+ rescue IOError => e
36
+ logger.fatal "[#{self.name}##{__method__.to_s}] Appforce::ConfigDumpError -- #{e}"
37
+ raise e
38
+ ensure
39
+ file.close unless file == nil
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def self.logger
46
+ Appforce::Logger.logger
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module Appforce
2
+ class Logger
3
+ require 'logger'
4
+
5
+ def self.logger
6
+ @logger
7
+ end
8
+
9
+ def self.logger=(logger)
10
+ @logger = logger
11
+ end
12
+
13
+ def self.header(str='')
14
+ output = ' == ' + str + ' '
15
+ adjust = 78 - output.length
16
+ adjust = adjust < 0 ? 0 : adjust
17
+ output << "="*adjust
18
+ end
19
+
20
+ def self.footer
21
+ " " + "="*77
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,312 @@
1
+ require 'httparty'
2
+
3
+ require 'appforce/config'
4
+ require 'appforce/logger'
5
+ require 'appforce/spawn/version'
6
+ require 'appforce/spawn/exceptions'
7
+ require 'appforce/spawn/api'
8
+ require 'appforce/spawn/template'
9
+ require 'appforce/spawn/runner'
10
+
11
+ module Appforce
12
+ module Spawn
13
+
14
+ ##
15
+ # Extend module base class
16
+ # Allows fo r class like interactions on Module
17
+ def self.included(base)
18
+ base.extend ClassMethods
19
+ end
20
+
21
+ # Define methods to extend
22
+ module ClassMethods
23
+
24
+ def logger
25
+ Appforce::Logger.logger
26
+ end
27
+
28
+ def logger=(logger)
29
+ Appforce::Logger.logger = logger
30
+ end
31
+
32
+ def header(input)
33
+ Appforce::Logger.header(input)
34
+ end
35
+
36
+ def footer
37
+ Appforce::Logger.footer
38
+ end
39
+
40
+ def ping_host
41
+ HTTParty.get("#{config.api_host}/ping")
42
+ end
43
+
44
+ def ping_api
45
+ Appforce::Spawn::Api::Call.ping
46
+ end
47
+
48
+ def load_config(*args)
49
+ Appforce::Config.load_config(*args)
50
+ end
51
+
52
+ def config
53
+ Appforce::Config.config
54
+ end
55
+
56
+ def clients
57
+ Appforce::Spawn::Api::Call.list_clients
58
+ end
59
+
60
+ def get_clients
61
+ Appforce::Spawn::Api::Call.get_clients
62
+ end
63
+
64
+ def get_hosts(*args)
65
+ Appforce::Spawn::Api::Call.get_hosts(*args)
66
+ end
67
+
68
+ def get_active_users(*args)
69
+ Appforce::Spawn::Api::Call.get_active_users(*args)
70
+ end
71
+
72
+ def get_inactive_users(*args)
73
+ Appforce::Spawn::Api::Call.get_inactive_users(*args)
74
+ end
75
+
76
+ def get_vars(*args)
77
+ Appforce::Spawn::Api::Call.get_vars(*args)
78
+ end
79
+
80
+ def generate(*args)
81
+ logger.info "[#{self.name}##{__method__.to_s}] Template Generation Starting"
82
+ Appforce::Spawn::Template.create_dirs(*args)
83
+ Appforce::Spawn::Template.copy_template(*args)
84
+ Appforce::Spawn::Template.download_files(*args)
85
+ logger.info "[#{self.name}##{__method__.to_s}] Template Generation Complete"
86
+ end
87
+
88
+ def spawn(*args)
89
+ Appforce::Spawn::Runner.can_run?
90
+ Appforce::Spawn::Runner.run_playbook
91
+ end
92
+
93
+ def display_ansible_command(*args)
94
+ Appforce::Spawn::Runner.can_run?
95
+ Appforce::Spawn::Runner.display_ansible_command
96
+ end
97
+
98
+ def ansible_ping(*args)
99
+ Appforce::Spawn::Runner.can_run?
100
+ Appforce::Spawn::Runner.ansible_ping
101
+ end
102
+
103
+ def display_ansible_ping_command(*args)
104
+ Appforce::Spawn::Runner.can_run?
105
+ Appforce::Spawn::Runner.display_ansible_ping_command
106
+ end
107
+
108
+ def dump_example_config(*args)
109
+ Appforce::Config.dump_example_config
110
+ end
111
+
112
+ def get_all_host_data(*args)
113
+ Appforce::Spawn::Api::Call.get_all_host_data(*args)
114
+ end
115
+
116
+ def ssh_to_host(*args)
117
+ Appforce::Spawn::Api::Call.ssh_to_host(*args)
118
+ end
119
+
120
+ def latest_version(*args)
121
+ Appforce::Spawn::Api::Call.latest_version
122
+ end
123
+
124
+ def check_dependencies(*args)
125
+ Appforce::Spawn::Runner.check_dependencies
126
+ logger.info "[#{self.name}##{__method__.to_s}] All required Ansible checks passed."
127
+ end
128
+ end
129
+
130
+ # Extension class
131
+ class Basement #:nodoc:
132
+ include Appforce::Spawn
133
+ end
134
+
135
+ def self.logger
136
+ Basement.logger
137
+ end
138
+
139
+ def self.logger=(logger)
140
+ Basement.logger = logger
141
+ end
142
+
143
+ def self.header(input)
144
+ Basement.header(input)
145
+ end
146
+
147
+ def self.footer
148
+ Basement.footer
149
+ end
150
+
151
+ def self.load_config(*args, &block)
152
+ Basement.load_config(*args, &block)
153
+ end
154
+
155
+ def self.config(*args)
156
+ Basement.config
157
+ end
158
+
159
+ def self.available_actions
160
+ {
161
+ 'clients' => {
162
+ info: "Retrieve a complete list of Clients",
163
+ cmd: :clients
164
+ },
165
+ 'generate' => {
166
+ info: "Build out the Ansible template for a Client (client API name is required)",
167
+ cmd: :generate
168
+ },
169
+ 'client:hosts' => {
170
+ info: "Retrieve a Hosts file for a Client (client API name is required)",
171
+ cmd: :get_hosts
172
+ },
173
+ 'client:hosts:details' => {
174
+ info: "Display all details about Client Hosts (client API name is required)",
175
+ cmd: :get_all_host_data
176
+ },
177
+ 'client:users:active' => {
178
+ info: "Retrieve an Active Users file for a Client (client API name is required)",
179
+ cmd: :get_active_users
180
+ },
181
+ 'client:users:inactive' => {
182
+ info: "Retrieve an Inactive Users file for a Client (client API name is required)",
183
+ cmd: :get_inactive_users
184
+ },
185
+ 'client:vars' => {
186
+ info: "Retrieve a Vars file for a Client (client API name is required)",
187
+ cmd: :get_vars
188
+ },
189
+ 'ping' => {
190
+ info: "Test connection to host and API access",
191
+ cmd: :ping
192
+ },
193
+ 'ping:host' => {
194
+ info: "Test if API is available",
195
+ cmd: :ping_host
196
+ },
197
+ 'ping:api' => {
198
+ info: "Test if you have access to the API",
199
+ cmd: :ping_api
200
+ },
201
+ 'spawn' => {
202
+ info: "Run Ansible Playbook to spawn users to Client hosts",
203
+ cmd: :spawn
204
+ },
205
+ 'spawn:ready?' => {
206
+ info: "Check to see if you have the required ansible version and roles installed",
207
+ cmd: :check_dependencies
208
+ },
209
+ 'spawn:command' => {
210
+ info: "Display Ansible command to run Playbook for a Client",
211
+ cmd: :display_ansible_command
212
+ },
213
+ 'spawn:ping' => {
214
+ info: "Ansible ping Client hosts in the 'hosts' file",
215
+ cmd: :ansible_ping
216
+ },
217
+ 'spawn:ping:command' => {
218
+ info: "Display ansible ping command",
219
+ cmd: :display_ansible_ping_command
220
+ },
221
+ 'ssh' => {
222
+ info: "Select a Host to ssh in as your User name (client API name is required)",
223
+ cmd: :ssh_to_host
224
+ },
225
+ }
226
+ end
227
+
228
+ def self.clients(*args)
229
+ Basement.clients
230
+ end
231
+
232
+ def self.get_clients(*args)
233
+ Basement.get_clients
234
+ end
235
+
236
+ def self.get_hosts(*args)
237
+ Basement.get_hosts(*args)
238
+ end
239
+
240
+ def self.get_active_users(*args)
241
+ Basement.get_active_users(*args)
242
+ end
243
+
244
+ def self.get_inactive_users(*args)
245
+ Basement.get_inactive_users(*args)
246
+ end
247
+
248
+ def self.get_vars(*args)
249
+ Basement.get_vars(*args)
250
+ end
251
+
252
+ def self.get_all_host_data(*args)
253
+ Basement.get_all_host_data(*args)
254
+ end
255
+
256
+ def self.generate(*args)
257
+ Basement.generate(*args)
258
+ end
259
+
260
+ def self.spawn(*args)
261
+ Basement.spawn
262
+ end
263
+
264
+ def self.display_ansible_command(*args)
265
+ Basement.display_ansible_command
266
+ end
267
+
268
+ def self.ansible_ping(*args)
269
+ Basement.ansible_ping
270
+ end
271
+
272
+ def self.display_ansible_ping_command(*args)
273
+ Basement.display_ansible_ping_command
274
+ end
275
+
276
+ def self.dump_example_config(*args)
277
+ Basement.dump_example_config(*args)
278
+ end
279
+
280
+ def self.ssh_to_host(*args)
281
+ Basement.ssh_to_host(*args)
282
+ end
283
+
284
+ def self.latest_version(*args)
285
+ Basement.latest_version(*args)
286
+ end
287
+
288
+ def self.check_dependencies(*args)
289
+ Basement.check_dependencies(*args)
290
+ end
291
+
292
+ def self.ping(*args)
293
+ host = Basement.ping_host
294
+ api = Basement.ping_api
295
+ {
296
+ :host => { :code => host.code, :body => host.body },
297
+ :api => { :code => api.code, :body => api.body }
298
+ }
299
+ end
300
+
301
+ def self.ping_host(*args)
302
+ host = Basement.ping_host
303
+ { :code => host.code, :body => host.body }
304
+ end
305
+
306
+ def self.ping_api(*args)
307
+ api = Basement.ping_api
308
+ { :code => api.code, :body => api.body }
309
+ end
310
+
311
+ end
312
+ end