inprovise 0.2.2

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 (56) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +28 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE +8 -0
  6. data/README.md +197 -0
  7. data/Rakefile.rb +9 -0
  8. data/bin/rig +5 -0
  9. data/inprovise.gemspec +22 -0
  10. data/lib/inprovise/channel/ssh.rb +202 -0
  11. data/lib/inprovise/cli/group.rb +86 -0
  12. data/lib/inprovise/cli/node.rb +95 -0
  13. data/lib/inprovise/cli/provision.rb +84 -0
  14. data/lib/inprovise/cli.rb +105 -0
  15. data/lib/inprovise/cmd_channel.rb +100 -0
  16. data/lib/inprovise/cmd_helper.rb +150 -0
  17. data/lib/inprovise/control.rb +326 -0
  18. data/lib/inprovise/execution_context.rb +277 -0
  19. data/lib/inprovise/group.rb +67 -0
  20. data/lib/inprovise/helper/cygwin.rb +43 -0
  21. data/lib/inprovise/helper/linux.rb +181 -0
  22. data/lib/inprovise/helper/windows.rb +123 -0
  23. data/lib/inprovise/infra.rb +122 -0
  24. data/lib/inprovise/local_file.rb +120 -0
  25. data/lib/inprovise/logger.rb +79 -0
  26. data/lib/inprovise/node.rb +271 -0
  27. data/lib/inprovise/remote_file.rb +128 -0
  28. data/lib/inprovise/resolver.rb +36 -0
  29. data/lib/inprovise/script.rb +175 -0
  30. data/lib/inprovise/script_index.rb +46 -0
  31. data/lib/inprovise/script_runner.rb +110 -0
  32. data/lib/inprovise/sniff.rb +46 -0
  33. data/lib/inprovise/sniffer/linux.rb +64 -0
  34. data/lib/inprovise/sniffer/platform.rb +46 -0
  35. data/lib/inprovise/sniffer/unknown.rb +11 -0
  36. data/lib/inprovise/sniffer/windows.rb +32 -0
  37. data/lib/inprovise/template/inprovise.rb.erb +92 -0
  38. data/lib/inprovise/template.rb +38 -0
  39. data/lib/inprovise/trigger_runner.rb +36 -0
  40. data/lib/inprovise/version.rb +10 -0
  41. data/lib/inprovise.rb +145 -0
  42. data/test/cli_test.rb +314 -0
  43. data/test/cli_test_helper.rb +19 -0
  44. data/test/dsl_test.rb +43 -0
  45. data/test/fixtures/example.txt +1 -0
  46. data/test/fixtures/include.rb +4 -0
  47. data/test/fixtures/inprovise.rb +1 -0
  48. data/test/fixtures/myscheme.rb +1 -0
  49. data/test/infra_test.rb +189 -0
  50. data/test/local_file_test.rb +64 -0
  51. data/test/remote_file_test.rb +106 -0
  52. data/test/resolver_test.rb +66 -0
  53. data/test/script_index_test.rb +53 -0
  54. data/test/script_test.rb +56 -0
  55. data/test/test_helper.rb +237 -0
  56. metadata +182 -0
@@ -0,0 +1,95 @@
1
+ # CLI Node commands for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ class Inprovise::Cli
7
+
8
+ desc 'Manage infrastructure nodes'
9
+ command :node do |cnod|
10
+
11
+ cnod.desc 'Add an infrastructure node'
12
+ cnod.arg_name 'NODE'
13
+ cnod.command :add do |cnod_add|
14
+
15
+ cnod_add.flag [:a, :address], :arg_name => 'ADDRESS', :desc => 'Set the node address (hostname or IP). If not set node name is used as hostname.'
16
+ cnod_add.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the node.'
17
+ cnod_add.flag [:C, :credential], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a security credential setting for the node.'
18
+ cnod_add.switch [:sniff], :default_value => true, :desc => 'Enable or disable running sniffers'
19
+ cnod_add.flag [:g, :group], :arg_name => 'GROUP', :multiple => true, :desc => 'Existing infrastructure group to add new node to.'
20
+
21
+ cnod_add.action do |global,options,args|
22
+ raise ArgumentError, 'Missing or too many arguments!' unless args.size == 1
23
+ Inprovise::Controller.run(:add, options, :node, *args)
24
+ Inprovise::Controller.wait!
25
+ end
26
+
27
+ end
28
+
29
+ cnod.desc 'Remove (an) infrastructure node(s)'
30
+ cnod.arg_name 'NODE[ NODE [...]]'
31
+ cnod.command :remove do |cnod_del|
32
+
33
+ cnod_del.action do |global,options,args|
34
+ raise ArgumentError, 'Missing argument!' if args.empty?
35
+ Inprovise::Controller.run(:remove, options, :node, *args)
36
+ Inprovise::Controller.wait!
37
+ end
38
+
39
+ end
40
+
41
+ cnod.desc 'Update node configuration for the given infrastructure node(s) or group(s).'
42
+ cnod.arg_name 'NAME[ NAME [...]]'
43
+ cnod.command :update do |cnod_update|
44
+
45
+ cnod_update.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the node(s)'
46
+ cnod_update.flag [:C, :credential], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a security credential setting for the node.'
47
+ cnod_update.switch [:r, :reset], negatable: false, :desc => 'Reset configuration before update (default is to merge updates)'
48
+ cnod_update.switch [:sniff], :default_value => true, :desc => 'Enable or disable running sniffers'
49
+ cnod_update.flag [:g, :group], :arg_name => 'GROUP', :multiple => true, :desc => 'Existing infrastructure group to add node(s) to.'
50
+
51
+ cnod_update.action do |global,options,args|
52
+ raise ArgumentError, 'Missing argument!' if args.empty?
53
+ Inprovise::Controller.run(:update, options, :node, *args)
54
+ Inprovise::Controller.wait!
55
+ end
56
+ end
57
+
58
+ cnod.desc 'List infrastructure nodes (all or for specified nodes/groups)'
59
+ cnod.arg_name '[NAME[ NAME [...]]]'
60
+ cnod.command :list do |cnod_list|
61
+ cnod_list.switch [:d, :details], negatable: false, :desc => 'Show node details'
62
+
63
+ cnod_list.action do |global_options,options,args|
64
+ $stdout.puts
65
+ $stdout.puts " INFRASTRUCTURE NODES"
66
+ $stdout.puts " ===================="
67
+ if args.empty?
68
+ Inprovise::Infrastructure.list(Inprovise::Infrastructure::Node).each do |n|
69
+ Inprovise::Cli.show_target(n, options[:details])
70
+ end
71
+ else
72
+ args.each do |a|
73
+ tgt = Inprovise::Infrastructure.find(a)
74
+ case tgt
75
+ when Inprovise::Infrastructure::Node
76
+ Inprovise::Cli.show_target(tgt, options[:details])
77
+ when Inprovise::Infrastructure::Group
78
+ $stdout.puts " #{tgt.to_s}"
79
+ $stdout.puts " #{'-' * tgt.to_s.size}"
80
+ tgt.targets.each {|n| Inprovise::Cli.show_target(n, options[:details]) }
81
+ $stdout.puts " #{'-' * tgt.to_s.size}"
82
+ else
83
+ $stdout.puts "ERROR: #{a} is unknown".red
84
+ end
85
+ end
86
+ end
87
+ $stdout.puts
88
+ end
89
+ end
90
+
91
+ cnod.default_command :list
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,84 @@
1
+ # CLI provisioning commands for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ class Inprovise::Cli
7
+
8
+ desc 'Apply the given script/package to the specified infrastructure nodes and/or groups.'
9
+ arg_name 'SCRIPT TARGET[ TARGET[...]]'
10
+ command :apply do |capply|
11
+
12
+ capply.desc 'Path to a provisioning scheme to load'
13
+ capply.flag [:s,:scheme], :arg_name => 'FILE', :multiple => true, :default_value => Inprovise.default_scheme
14
+ capply.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the script execution'
15
+
16
+ capply.action do |global, options, args|
17
+ raise ArgumentError, 'Missing arguments!' if args.empty?
18
+ raise ArgumentError, 'Missing targets!' if args.size < 2
19
+ Inprovise::Controller.run(:apply, options, *args)
20
+ Inprovise::Controller.wait!
21
+ end
22
+ end
23
+
24
+ desc 'Revert the given script/package on the specified infrastructure nodes and/or groups.'
25
+ arg_name 'SCRIPT NAME[ NAME[...]]'
26
+ command :revert do |crevert|
27
+
28
+ crevert.desc 'Path to a provisioning scheme to load'
29
+ crevert.flag [:s,:scheme], :arg_name => 'FILE', :multiple => true, :default_value => Inprovise.default_scheme
30
+ crevert.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the script execution'
31
+
32
+ crevert.action do |global, options, args|
33
+ raise ArgumentError, 'Missing arguments!' if args.empty?
34
+ raise ArgumentError, 'Missing targets!' if args.size < 2
35
+ Inprovise::Controller.run(:revert, options, *args)
36
+ Inprovise::Controller.wait!
37
+ end
38
+ end
39
+
40
+ desc 'Validate the given script/package on the specified infrastructure nodes and/or groups.'
41
+ arg_name 'SCRIPT NAME[ NAME[...]]'
42
+ command :validate do |cvalid|
43
+
44
+ cvalid.desc 'Path to a provisioning scheme to load'
45
+ cvalid.flag [:s,:scheme], :arg_name => 'FILE', :multiple => true, :default_value => Inprovise.default_scheme
46
+ cvalid.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the script execution'
47
+
48
+ cvalid.action do |global, options, args|
49
+ raise ArgumentError, 'Missing arguments!' if args.empty?
50
+ raise ArgumentError, 'Missing targets!' if args.size < 2
51
+ Inprovise::Controller.run(:validate, options, *args)
52
+ Inprovise::Controller.wait!
53
+ end
54
+ end
55
+
56
+ desc 'Trigger a specific action on the specified infrastructure nodes and/or groups.'
57
+ arg_name 'ACTION NAME[ NAME[...]]'
58
+ command :trigger do |ctrigger|
59
+
60
+ ctrigger.desc 'Path to a provisioning scheme to load'
61
+ ctrigger.flag [:s,:scheme], :arg_name => 'FILE', :multiple => true, :default_value => Inprovise.default_scheme
62
+ ctrigger.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the script execution'
63
+
64
+ ctrigger.action do |global, options, args|
65
+ raise ArgumentError, 'Missing arguments!' if args.empty?
66
+ raise ArgumentError, 'Missing targets!' if args.size < 2
67
+ Inprovise::Controller.run(:trigger, options, *args)
68
+ Inprovise::Controller.wait!
69
+ end
70
+ end
71
+
72
+ desc 'List the available scripts. By default lists only described scripts.'
73
+ command :list do |clist|
74
+
75
+ clist.desc 'Path to a provisioning scheme to load'
76
+ clist.flag [:s,:scheme], :arg_name => 'FILE', :multiple => true, :default_value => Inprovise.default_scheme
77
+ clist.switch [:a, :all], negatable: false, :desc => 'List all scripts (with or without description)'
78
+
79
+ clist.action do |global, options, args|
80
+ Inprovise::Controller.list_scripts(options)
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,105 @@
1
+ # CLI for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require 'gli'
7
+ require 'fileutils'
8
+
9
+ class Inprovise::Cli
10
+
11
+ class << self
12
+ include GLI::App
13
+ end
14
+
15
+
16
+ program_desc 'CLI for Inprovise'
17
+
18
+ version Inprovise::VERSION
19
+
20
+ subcommand_option_handling :normal
21
+ arguments :strict
22
+ sort_help :manually
23
+
24
+ desc 'Don\'t actually run any commands on the group, just pretend.'
25
+ switch [:n,:'dry-run'], {negatable: false}
26
+
27
+ desc 'Increase verbosity, useful for debugging.'
28
+ flag [:v, :verbose], :arg_name => 'LEVEL', :default_value => 0, :type => Integer
29
+
30
+ desc 'Show exception backtraces on exit.'
31
+ switch [:x, :'show-backtrace'], {negatable: false}
32
+
33
+ desc 'Don\'t run tasks in parrallel across nodes.'
34
+ switch [:sequential], {negatable: false}
35
+
36
+ desc 'Don\'t validate and run dependencies.'
37
+ switch [:'skip-dependencies'], {negatable: false}
38
+
39
+ require_relative './cli/node'
40
+ require_relative './cli/group'
41
+ require_relative './cli/provision'
42
+
43
+ desc 'Initialize Inprovise project.'
44
+ command :init do |cinit|
45
+ cinit.action do |global,options,args|
46
+ raise RuntimeError, 'Cannot initialize existing project directory.' if File.exists?(Inprovise::INFRA_FILE)
47
+ raise RuntimeError, "Default scheme #{Inprovise.default_scheme} already exists." if File.exists?(Inprovise.default_scheme)
48
+ begin
49
+ Inprovise::Infrastructure.init(Inprovise::INFRA_FILE)
50
+ path = Inprovise::Template.new(File.join(File.dirname(__FILE__),'template','inprovise.rb.erb')).render_to_tempfile
51
+ FileUtils.mv(path, Inprovise.default_scheme)
52
+ rescue
53
+ File.delete(Inprovise::INFRA_FILE) if File.exists?(Inprovise::INFRA_FILE)
54
+ File.delete(Inprovise.default_scheme) if File.exists?(Inprovise.default_scheme)
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ pre do |global,command,options,args|
61
+ # Pre logic here
62
+ # Return true to proceed; false to abort and not call the
63
+ # chosen command
64
+ # Use skips_pre before a command to skip this block
65
+ # on that command only
66
+ Inprovise.verbosity = global[:verbose] || 0
67
+ Inprovise.show_backtrace = global[:'show-backtrace']
68
+ Inprovise.sequential = global[:sequential]
69
+ Inprovise.demonstrate = global[:demonstrate]
70
+ Inprovise.skip_dependencies = global[:'skip-dependencies']
71
+ unless command.name == :init
72
+ if File.readable?(File.join(Inprovise.root, Inprovise::RC_FILE))
73
+ Inprovise.log.local("Loading #{Inprovise::RC_FILE}") if Inprovise.verbosity > 1
74
+ load File.join(Inprovise.root, Inprovise::RC_FILE)
75
+ end
76
+
77
+ Inprovise::Infrastructure.load
78
+ end
79
+ true
80
+ end
81
+
82
+ post do |global,command,options,args|
83
+ # Post logic here
84
+ # Use skips_post before a command to skip this
85
+ # block on that command only
86
+ end
87
+
88
+ on_error do |exception|
89
+ # Error logic here
90
+ # return false to skip default error handling
91
+ $stderr.puts "ERROR: #{exception.message}".red
92
+ if Inprovise.show_backtrace
93
+ $stderr.puts "#{exception}\n#{exception.backtrace.join("\n")}"
94
+ end
95
+ exit 1
96
+ end
97
+
98
+ def self.show_target(tgt, details=false)
99
+ $stdout.puts " #{tgt.to_s}"
100
+ if details
101
+ $stdout.puts " \t"+JSON.pretty_generate(tgt.config).split("\n").join("\n \t")
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,100 @@
1
+ # Command channel for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ module Inprovise::CmdChannel
7
+
8
+ class << self
9
+
10
+ def implementations
11
+ @implementations ||= {}
12
+ end
13
+
14
+ def default_implementation
15
+ @default ||= 'ssh'
16
+ end
17
+
18
+ def default_implementation=(impl)
19
+ @default = impl
20
+ end
21
+
22
+ def define(impl, base=::Object, &definition)
23
+ implklass = Class.new(base) do
24
+ include Inprovise::CmdChannel
25
+ end
26
+ implklass.class_eval(&definition)
27
+ implementations[impl.to_s] = implklass
28
+ implklass
29
+ end
30
+
31
+ def open(node, impl)
32
+ implementations[impl || default_implementation].new(node)
33
+ end
34
+
35
+ end
36
+
37
+ attr_reader :node
38
+
39
+ def initialize(node)
40
+ @node = node
41
+ end
42
+
43
+ # session management
44
+
45
+ def close
46
+ # noop
47
+ end
48
+
49
+ # command execution (MANDATORY)
50
+
51
+ def run(command, forcelog=false)
52
+ raise RuntimeError, 'UNIMPLEMENTED'
53
+ end
54
+
55
+ # MANDATORY file management routines
56
+
57
+ def upload(from, to)
58
+ raise RuntimeError, 'UNIMPLEMENTED'
59
+ end
60
+
61
+ def download(from, to)
62
+ raise RuntimeError, 'UNIMPLEMENTED'
63
+ end
64
+
65
+ # OPTIONAL file management routines
66
+
67
+ # not recursive
68
+ # def mkdir(path)
69
+ # end
70
+
71
+ # def exists?(path)
72
+ # end
73
+
74
+ # def file?
75
+ # end
76
+ #
77
+ # def directory?
78
+ # end
79
+
80
+ # def content
81
+ # end
82
+
83
+ # def delete(path)
84
+ # end
85
+
86
+ # def permissions(path)
87
+ # end
88
+ #
89
+ # def set_permissions(path, perm)
90
+ # end
91
+
92
+ # def owner(path)
93
+ # end
94
+ #
95
+ # def set_owner(path, user, group=nil)
96
+ # end
97
+
98
+ end
99
+
100
+ require_relative './channel/ssh'
@@ -0,0 +1,150 @@
1
+ # Command helper for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ module Inprovise::CmdHelper
7
+
8
+ class << self
9
+
10
+ def implementations
11
+ @implementations ||= {}
12
+ end
13
+
14
+ def default_implementation
15
+ @default ||= 'linux'
16
+ end
17
+
18
+ def default_implementation=(impl)
19
+ @default = impl
20
+ end
21
+
22
+ def define(impl, base=::Object, &definition)
23
+ implklass = Class.new(base) do
24
+ include Inprovise::CmdHelper
25
+ end
26
+ implklass.class_eval(&definition)
27
+ implementations[impl.to_s] = implklass
28
+ implklass
29
+ end
30
+
31
+ def get(node, impl)
32
+ implementations[impl || default_implementation].new(node.channel)
33
+ end
34
+
35
+ end
36
+
37
+ attr_reader :channel
38
+
39
+ # default init
40
+ def initialize(channel)
41
+ @channel = channel
42
+ end
43
+
44
+ # platform properties
45
+
46
+ def admin_user
47
+ nil
48
+ end
49
+
50
+ def env_reference(varname)
51
+ nil
52
+ end
53
+
54
+ def cwd
55
+ nil
56
+ end
57
+
58
+ # *must* return previous value
59
+ def set_cwd(path)
60
+ nil
61
+ end
62
+
63
+ # generic command execution
64
+
65
+ def run(cmd, forcelog=false)
66
+ @channel.run(cmd,forcelog)
67
+ end
68
+
69
+ # return sudo helper
70
+ def sudo
71
+ nil
72
+ end
73
+
74
+ # file management
75
+
76
+ def upload(from, to)
77
+ @channel.upload(from, to)
78
+ end
79
+
80
+ def download(from, to)
81
+ @channel.download(from, to)
82
+ end
83
+
84
+ # basic commands
85
+
86
+ def echo(arg)
87
+ nil
88
+ end
89
+
90
+ def env(var)
91
+ echo(env_reference(var))
92
+ end
93
+
94
+ def cat(path)
95
+ nil
96
+ end
97
+
98
+ def hash_for(path)
99
+ nil
100
+ end
101
+
102
+ def mkdir(path)
103
+ nil
104
+ end
105
+
106
+ def exists?(path)
107
+ false
108
+ end
109
+
110
+ def file?(path)
111
+ false
112
+ end
113
+
114
+ def directory?(path)
115
+ false
116
+ end
117
+
118
+ def copy(from, to)
119
+ nil
120
+ end
121
+
122
+ def delete(path)
123
+ nil
124
+ end
125
+
126
+ def permissions(path)
127
+ 0
128
+ end
129
+
130
+ def set_permissions(path, perm)
131
+ nil
132
+ end
133
+
134
+ def owner(path)
135
+ nil
136
+ end
137
+
138
+ def set_owner(path, user, group=nil)
139
+ nil
140
+ end
141
+
142
+ def binary_exists?(bin)
143
+ false
144
+ end
145
+
146
+ end
147
+
148
+ require_relative './helper/linux'
149
+ require_relative './helper/cygwin'
150
+ require_relative './helper/windows'