inprovise 0.2.2

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