appforce-spawn 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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