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
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YTllYTMyMWNhN2ViNTk2YWQ1M2ZlYWUxNDA1ZWJhNjYxMDhiNmE1OQ==
5
+ data.tar.gz: !binary |-
6
+ YTJkNjFhNjkxODU5ZGU1MDE5YmRlNWU5OGQxYzE0Y2RkNzU0ZjVhZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZGFmYzI0MGUwYWIyODY3NzM3MDNkNjI1NjQzZTVlNDZhZDViMzAwOTM1ZWFh
10
+ MGNlYWE4YTU5ZDc2ZjA2YzA4ODA2ZTk1Mjk2OTQ2MmY3ZDUxZmU2OTZkNjk5
11
+ YzQ4ZjRhYmQ2ODFlNDFmY2RkMzVjMDI4ZThjYTgyOTk4M2M4NDg=
12
+ data.tar.gz: !binary |-
13
+ ZGU3M2M4NzY2OGJlMmY3NGFiOTJmZGVjOWY3YTE0MTQ4YjZmZWUxNGQ5OWEx
14
+ MzU4MWRiZDE3YWY2NDdjY2U2MzViNWU2NWE1MjYxYzcxOTYwN2ExYTA1NTFi
15
+ ZmVmYmEyMWY2Yjk0ZmIwNmJlZTBmNDBhZDNhZmVlNDY3M2UzOTA=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+
2
+ .*
3
+ pkg/*.gem
4
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,28 @@
1
+ before_install:
2
+ - gem install bundler
3
+ bundler_args: "--verbose"
4
+ script: rake test
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.1
8
+ - 2.2
9
+ - 2.3.1
10
+ - ruby-head
11
+ gemfile:
12
+ - Gemfile
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: ruby-head
16
+ env: CODECLIMATE_REPO_TOKEN=2b46019558c79326fc3cc87f2e4261610256aee236063c4b62b8ed8d2c2ce21d
17
+ bundle exec rake
18
+ addons:
19
+ code_climate:
20
+ repo_token: 2b46019558c79326fc3cc87f2e4261610256aee236063c4b62b8ed8d2c2ce21d
21
+ deploy:
22
+ provider: rubygems
23
+ api_key:
24
+ secure: nkx4D7AGMCSZrWXj+KLwk+3PFErq70xiKSg8oY0woeBxQbLcWkqZaR0j6p8OI5rAJNN5bDQbdylnq9J+YS8rzNOFC/V8CUlgQp/AhWIfsd9wezAjcu820XHw/4nPPWdD5TKb8Zjb/P25h0y4SpkSaysaJzOTELfdaNXEcKC8LNW+sLXKgMBByj6aMmw0gRPHZcZRb6KjnJAHsDa+vUhZhXPO9VecBk5SToFHK145zW52HA2Y2Th9iCKhl5p6+x5ENy81fW51ub2IBjFl+oGS1nW5xE62b6yLRKhjyMv0P69UhTP+AxHBo/Ef0Ke4JAzlAszbS4ymPKjVkOdto2gRf3jvO82tUnvIvv7F+a5oi6R61ZFM0S5fNvU0rxFcHt88spjU+DZuvNx8Yxqu3U2Iy65qeDOhCJOPjyVW5X9b20l7nqKEXZL7syPxSFwX/iq8utZRjgF/8rUkS26LhRQAhT1uHme5YdrJLVNw7MdZxlRiSDfzVOPVitvK34V71lPrK16U60crZ2HUCn7Bq93ENMkNRaIHAGrsTmHxCzX+G7nsnlh8CuErEtDvNt9tQZXjNl7EE4w+4srULS8SExDNt6LMpEGDFNtMtr9Hka3MAdgFs+Qz1p5LtV0uKsD5h1jLk0X7HBrJRMoMjEtNS5WVtI0tpDx2y6mB49nGLeHMvhY=
25
+ gemspec: inprovise.gemspec
26
+ on:
27
+ tags: true
28
+ repo: mcorino/Inprovise
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'minitest'
7
+ gem 'mocha', :require => false
8
+
9
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2016 Martin Corino
2
+ Portions Copyright (c) 2012 Andrew Kent
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+
2
+ Inprovise
3
+ =========
4
+
5
+ **Because provisioning small computing infrastructures should be simple, intuitive and NOT require additional infrastructure or elaborate setups just to run your provisioning scripts.**
6
+
7
+ Inprovise (Intuitive Provisioning Environment) is a super simple way to provision servers and virtual machines.
8
+
9
+ [![Build Status](https://travis-ci.org/mcorino/Inprovise.png)](https://travis-ci.org/mcorino/Inprovise)
10
+ [![Code Climate](https://codeclimate.com/github/mcorino/Inprovise/badges/gpa.png)](https://codeclimate.com/github/mcorino/Inprovise)
11
+ [![Test Coverage](https://codeclimate.com/github/mcorino/Inprovise/badges/coverage.png)](https://codeclimate.com/github/mcorino/Inprovise/coverage)
12
+
13
+ If you've found yourself stuck in the gap between deployment tools like Capistrano and full blown infrastructure tools like Puppet and Chef then Inprovise might be a good fit for you.
14
+ This is especially the case if you choose to cycle machines and prefer baking from scratch when changes are required rather than attempting to converge system state
15
+ (although Inprovise is flexible and extensible enough to achieve anything you would like).
16
+
17
+ Acknowledgement
18
+ ---------------
19
+
20
+ First off a very big acknowledgement.
21
+ When searching (yet again) for a usable (as in 'easy' and 'intuitive') tool for managing the provisioning of our smallish computing farm
22
+ (consisting of <10 host servers each running 6-12 VMs each) and yet again becoming disappointed by the requirements and overkill offered by tools like
23
+ Chef and Puppet, I finally found the *Orca* tool by Andy Kent (https://github.com/andykent/orca).
24
+ *This* was what I was thinking of!
25
+
26
+ Unfortunately Andy some time ago deprecated his project and there were some aspects of Orca I did not really like (a major one being the definition of the infrastructure nodes and groups inside the provisioning schemes)
27
+ as well as some missing bits (like more flexibility in the platform support).
28
+ I really did like the agent-less setup and the simple and elegant structure of the DSL though as well as the fact it was written in one of my favorite programming languages. As Andy's arguments for discontinuing
29
+ did not apply to us I decided to take up his code and rework it to my ideas and provide it with a minimum of multi platform support to also be able to manage the non-*nix nodes we needed to provision.
30
+
31
+ As Andy indicated the Orca name was (to be) handed over to another gem maintainer and I did not particularly like it anyway (sorry Andy) I renamed the package. Andy's code proved quite resilient to my changes
32
+ though and (apart from the name changes) I was able to copy large chunks more or less verbatim and rework those step by step thereby increasing my coding production significantly.
33
+
34
+ What problem does Inprovise try to solve?
35
+ ------------------------------------
36
+
37
+ All too often you need to get a new server up and running to a known state so that you can get an app deployed. Before Inprovise (and Orca) there were broadly 4 options...
38
+
39
+ 1. Start from scratch and hand install all the packages, files, permissions, etc. yourself over SSH.
40
+ 2. Use a deployment tool like Capistrano to codeify your shell scripts into semi-reusable steps.
41
+ 3. Use Puppet or Chef in single machine mode, requiring to install these tools on each host server.
42
+ 4. Use Full blown Puppet or Chef, this requires a server.
43
+
44
+ Inprovise fills the rather large gap between (2) and (3). It's a bigger gap then you think as both Puppet and Chef require...
45
+
46
+ - bootstrapping a machine to a point where you are able to run them
47
+ - Creating a seperate repository describing the configuration you require
48
+ - learning their complex syntaxes and structures
49
+ - hiding the differences of different host OSes
50
+
51
+ Inprovise fixes these problems by...
52
+
53
+ - working directly over standardized protocols (SSH by default), all you need is a box that you can connect to
54
+ - Inprovise maintains a simple (JSON) file based registry of your infrastructure
55
+ - scripting definitions can all go in a single file (although Inprovise supports modularization) and most servers can be configured in ~50 lines
56
+ - scripts are defined in a ruby based DSL that consists of a very small number of basic commands to learn
57
+ - Inprovise only requires a minimum operations (for cmd execution and file management) to be supported for any OS the details of which are abstracted through configurable handlers
58
+ - apart from the protocol and minimal operation set Inprovise makes no assumptions about the underlying OS
59
+ - Inprovise is extensible and adding platform specific features like package manager support can be achieved in a small amount of code using the core support
60
+
61
+
62
+ What problems is Inprovise avoiding?
63
+ -------------------------------
64
+
65
+ Inprovise intentionally skirts around some important things that may or may not matter to you.
66
+ If they do then you are probably better using tools such as Puppet or Chef.
67
+
68
+ Inprovise doesn't...
69
+
70
+ - try to scale beyond a smallish (2-100) number of nodes
71
+ - have any algorithms that attempt to run periodically and converge divergent configurations
72
+ - fully abstract the differences of different host OSes (in particular specific system support options and package management)
73
+ - provide a server to supervise infrastructure configuration
74
+
75
+
76
+ Installation
77
+ ------------
78
+
79
+ To install Inprovise you will need to be running Ruby 2+ and then install the inprovise gem...
80
+
81
+ gem install inprovise
82
+
83
+ or ideally add it to your gemfile...
84
+
85
+ gem 'inprovise'
86
+
87
+
88
+ Command Line Usage
89
+ ------------------
90
+
91
+ Inprovise provides a CLI tool called `rig`
92
+
93
+ To get started from within your project you can run...
94
+
95
+ rig init
96
+
97
+ This will create an empty infra.json and an example inprovise.rb file for you to get started with.
98
+
99
+ To manage nodes you can run...
100
+
101
+ rig node [add|remove|update] [options] [arguments]
102
+
103
+ To manage groups you can run...
104
+
105
+ rig group [add|remove|update] [options] [arguments]
106
+
107
+ To run a command the syntax is as follows...
108
+
109
+ rig [command] [script] [group_or_node]
110
+
111
+ So here are some examples (assuming you have a script called "app" and a node called "server" defined)...
112
+
113
+ rig apply app server
114
+ rig revert app server
115
+ rig validate app server
116
+
117
+ If you have a script with the same name as a group or node you can abreviate this to...
118
+
119
+ rig apply server
120
+ rig remove server
121
+ rig validate server
122
+
123
+ You can also directly trigger actions from the CLI like so...
124
+
125
+ rig trigger nginx:reload web-1
126
+ rig trigger firewall:add[allow,80] web-1
127
+
128
+ Options, all commands support the following optional parameters...
129
+
130
+ --demonstrate | dont actually run any commands just pretend like you are
131
+ --sequential | dont attempt to run commands across multiple nodes in parrallel
132
+ --verbose=LEVEL | increase logging level to see exceptions printed as well as SSH commands and results
133
+ --skip-dependencies | Don't validate and run dependancies, only the script specified
134
+
135
+ The `help` command shows basic or command specific help info if you run...
136
+
137
+ rig help [command]
138
+
139
+ The Inprovise DSL
140
+ ------------
141
+
142
+ Inprovise provides a Ruby based DSL to write your provisioning specifications. Files containing these provisioning
143
+ specs are called `schemes`.
144
+ Inprovise provisioning schemes are pure Ruby code and should preferably be stored in files with the '.rb' extension. When
145
+ no scheme is specified Inprovise looks for the scheme `inprovise.rb` in the projects root (where the `infa.json` is located) by default.
146
+ Scheme scripts are really simple to learn in less than 5 mins. Below is an example inprovise.rb file with some hints to help you get started.
147
+ A more complete WIP example can be found in this gist... https://gist.github.com/andykent/5814997
148
+
149
+ ````ruby
150
+ # define a new script called 'gem' that provides some actions for managing rubygems
151
+ script 'gem' do
152
+ depends_on 'ruby-1.9.3' # this script depends on another script called ruby-1.9.3
153
+ action 'exists' do |gem_name| # define an action that other scripts can trigger called 'exists'
154
+ run("gem list -i #{gem_name}") =~ /true/ # execute the command, get the output and check it contains 'true'
155
+ end
156
+ action 'install' do |gem_name|
157
+ run "gem install #{gem_name} --no-ri --no-rdoc"
158
+ end
159
+ action 'uninstall' do |gem_name|
160
+ run "gem uninstall #{gem_name} -x -a"
161
+ end
162
+ end
163
+
164
+ # define a script called 'bundler' that can be used to manage the gem by the same name
165
+ script 'bundler' do
166
+ depends_on 'gem'
167
+ apply do # apply gets called whenever this script or a script that depends on it is applied
168
+ trigger('gem:install', 'bundler') # trigger triggers defined actions, in this case the action 'install' on 'gem'
169
+ end
170
+ remove do # remove gets called whenever this script or a script that depends on it is removed
171
+ trigger('gem:uninstall', 'bundler')
172
+ end
173
+ validate do # validate is used internally to check if the script is applied correctly or not
174
+ trigger('gem:exists', 'bundler') # validate should return true if the script is applied correctly
175
+ end
176
+ end
177
+ ````
178
+
179
+ Configuration
180
+ -------------
181
+
182
+ The `rig` CLI tool loads a file named 'rigrc' at startup if available in the root of your project (where the 'infra.json' is located). This file is assumed
183
+ to contain pure Ruby code and is loaded as such. You can use this to require and setup any libraries, extensions etc. you want to be available to your
184
+ scripts defined in your project's scheme files.
185
+
186
+ As the scheme files themselves are also pure Ruby code you can also put configuration code there if that suites your use case better (for example if certain settings
187
+ should only be available to scripts defined in one particular scheme file).
188
+
189
+ Extensions
190
+ ----------
191
+
192
+ The core of Inprovise only provides a minimum of platform specific logic but is designed to be a foundation to build apon.
193
+ Basically the core currently supports using the SSH(+SFTP) protocol and provides operation handlers for `linux` and `cygwin` type OS environments.
194
+
195
+ Extensions can be written in their own files, projects or gems and loaded through the `rigrc` config file or any of your project's scheme files.
196
+ As these files are all pure Ruby code you can use 'require' statements and/or any other valid Ruby code to initialize your extensions.
197
+
data/Rakefile.rb ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+
9
+ task :default => :test
data/bin/rig ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/inprovise'
4
+
5
+ exit Inprovise::Cli.run(ARGV)
data/inprovise.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ require File.join(File.dirname(__FILE__), 'lib/inprovise/version')
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Martin Corino"]
5
+ gem.email = ["mcorino@remedy.nl"]
6
+ gem.description = %q{InProvisE is Intuitive Provisioning Environment}
7
+ gem.summary = %q{Simple, easy and intuitive infrastructure provisioning}
8
+ gem.homepage = ""
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "inprovise"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Inprovise::VERSION
16
+ gem.add_dependency('colored')
17
+ gem.add_dependency('net-ssh')
18
+ gem.add_dependency('net-sftp')
19
+ gem.add_dependency('gli')
20
+ gem.add_dependency('tilt')
21
+ gem.post_install_message = ''
22
+ end
@@ -0,0 +1,202 @@
1
+ # SSH Command channel for Inprovise
2
+ #
3
+ # Author:: Martin Corino
4
+ # License:: Distributes under the same license as Ruby
5
+
6
+ require 'net/ssh'
7
+ require 'net/sftp'
8
+ require 'digest/sha1'
9
+
10
+ Inprovise::CmdChannel.define('ssh') do
11
+
12
+ def initialize(node)
13
+ super(node)
14
+ @connection = nil
15
+ @sftp = nil
16
+ if @node.config.has_key?(:credentials) && @node.config[:credentials].has_key?(:'public-key')
17
+ install_pubkey(@node.config[:credentials][:'public-key'])
18
+ end
19
+ end
20
+
21
+ def close
22
+ disconnect
23
+ end
24
+
25
+ # command execution
26
+
27
+ def run(command, forcelog=false)
28
+ execute(command, forcelog)
29
+ end
30
+
31
+ # file management
32
+
33
+ def upload(from, to)
34
+ @node.log.remote("SFTP.UPLOAD: #{from} => #{to}") if Inprovise.verbosity > 1
35
+ sftp.upload!(from, to)
36
+ end
37
+
38
+ def download(from, to)
39
+ @node.log.remote("SFTP.DOWNLOAD: #{to} <= #{from}") if Inprovise.verbosity > 1
40
+ sftp.download!(from, to)
41
+ end
42
+
43
+ def mkdir(path)
44
+ @node.log.remote("SFTP.MKDIR: #{path}") if Inprovise.verbosity > 1
45
+ sftp.mkdir!(path)
46
+ end
47
+
48
+ def exists?(path)
49
+ @node.log.remote("SFTP.EXISTS?: #{path}") if Inprovise.verbosity > 1
50
+ begin
51
+ sftp.stat!(path) != nil
52
+ rescue Net::SFTP::StatusException => ex
53
+ raise ex unless ex.code == Net::SFTP::Response::FX_NO_SUCH_FILE
54
+ false
55
+ end
56
+ end
57
+
58
+ def file?(path)
59
+ @node.log.remote("SFTP.FILE?: #{path}") if Inprovise.verbosity > 1
60
+ begin
61
+ sftp.stat!(path).symbolic_type == :regular
62
+ rescue Net::SFTP::StatusException => ex
63
+ raise ex unless ex.code == Net::SFTP::Response::FX_NO_SUCH_FILE
64
+ false
65
+ end
66
+ end
67
+
68
+ def directory?(path)
69
+ @node.log.remote("SFTP.DIRECTORY?: #{path}") if Inprovise.verbosity > 1
70
+ begin
71
+ sftp.stat!(path).symbolic_type == :directory
72
+ rescue Net::SFTP::StatusException => ex
73
+ raise ex unless ex.code == Net::SFTP::Response::FX_NO_SUCH_FILE
74
+ false
75
+ end
76
+ end
77
+
78
+ def content(path)
79
+ @node.log.remote("SFTP.READ: #{path}") if Inprovise.verbosity > 1
80
+ sftp.file.open(path) do |io|
81
+ return io.read
82
+ end
83
+ end
84
+
85
+ def delete(path)
86
+ @node.log.remote("SFTP.DELETE: #{path}") if Inprovise.verbosity > 1
87
+ sftp.delete!(path) if exists?(path)
88
+ end
89
+
90
+ def permissions(path)
91
+ @node.log.remote("SFTP.PERMISSIONS: #{path}") if Inprovise.verbosity > 1
92
+ begin
93
+ sftp.stat!(path).permissions & 0x0FFF
94
+ rescue Net::SFTP::StatusException => ex
95
+ raise ex unless ex.code == Net::SFTP::Response::FX_NO_SUCH_FILE
96
+ 0
97
+ end
98
+ end
99
+
100
+ def set_permissions(path, perm)
101
+ @node.log.remote("SFTP.SETPERMISSIONS: #{path} #{'%o' % perm}") if Inprovise.verbosity > 1
102
+ sftp.setstat!(path, :permissions => perm)
103
+ end
104
+
105
+ def owner(path)
106
+ @node.log.remote("SFTP.OWNER: #{path}") if Inprovise.verbosity > 1
107
+ begin
108
+ result = sftp.stat!(path)
109
+ {:user => result.owner, :group => result.group}
110
+ rescue Net::SFTP::StatusException => ex
111
+ raise ex unless ex.code == Net::SFTP::Response::FX_NO_SUCH_FILE
112
+ nil
113
+ end
114
+ end
115
+
116
+ def set_owner(path, user, group=nil)
117
+ @node.log.remote("SFTP.SET_OWNER: #{path} #{user} #{group}") if Inprovise.verbosity > 1
118
+ attrs = { :owner => user }
119
+ attrs.merge({ :group => group }) if group
120
+ sftp.setstat!(path, attrs)
121
+ end
122
+
123
+ private
124
+
125
+ def options_for_ssh
126
+ opts = [
127
+ :auth_methods, :compression, :compression_level, :config, :encryption , :forward_agent , :global_known_hosts_file ,
128
+ :hmac , :host_key , :host_key_alias , :host_name, :kex , :keys , :key_data , :keys_only , :logger , :paranoid ,
129
+ :passphrase , :password , :port , :properties , :proxy , :rekey_blocks_limit , :rekey_limit , :rekey_packet_limit ,
130
+ :timeout , :user , :user_known_hosts_file , :verbose ]
131
+ ssh_cfg = @node.config.reduce({}) do |hsh, (k,v)|
132
+ hsh[k] = v if opts.include?(k)
133
+ hsh
134
+ end
135
+ (@node.config[:credentials] || {}).reduce(ssh_cfg) do |hsh, (k,v)|
136
+ hsh[k] = v if k == :password || k == :passphrase
137
+ hsh
138
+ end
139
+ end
140
+
141
+ def install_pubkey(pubkey_path)
142
+ log_bak = @node.log
143
+ begin
144
+ @node.log_to(Inprovise::Logger.new(@node, 'ssh[init]'))
145
+ unless exists?('./.ssh')
146
+ mkdir('./.ssh') rescue run('mkdir .ssh')
147
+ set_permissions('./.ssh', 755) rescue run('chmod 0755 ./.ssh')
148
+ end
149
+ pubkey = File.read(pubkey_path)
150
+ # check if public key already configured
151
+ if exists?('./.ssh/authorized_keys')
152
+ auth_keys = begin
153
+ content('./.ssh/authorized_keys')
154
+ rescue
155
+ run('cat ./.ssh/authorized_keys')
156
+ end.split("\n")
157
+ return if auth_keys.any? { |key| key == pubkey }
158
+ end
159
+ begin
160
+ @node.log.remote("APPEND: #{pubkey_path} -> ./.ssh/authorized_keys") if Inprovise.verbosity > 0
161
+ sftp.file.open('./.ssh/authorized_keys', 'a') do |f|
162
+ f.puts pubkey
163
+ end
164
+ rescue
165
+ # using the SFTP option failed, let's try a more basic approach
166
+ upload_path = "inprovise-upload-#{Digest::SHA1.file(pubkey_path).hexdigest}"
167
+ upload(pubkey_path, upload_path)
168
+ run("cat #{upload_path} >> ./.ssh/authorized_keys")
169
+ run("rm #{upload_path}")
170
+ end
171
+ set_permissions('./.ssh/authorized_keys', 644) rescue run('chmod 0644 ./.ssh/authorized_keys')
172
+ ensure
173
+ @node.log_to(log_bak)
174
+ end
175
+ end
176
+
177
+ def execute(cmd, forcelog=false)
178
+ @node.log.remote("SSH: #{cmd}") if Inprovise.verbosity > 1 || forcelog
179
+ output = ''
180
+ connection.exec! cmd do |channel, stream, data|
181
+ output << data if stream == :stdout
182
+ data.split("\n").each do |line|
183
+ @node.log.send(stream, line, forcelog)
184
+ end if Inprovise.verbosity > 1 || forcelog
185
+ end
186
+ output
187
+ end
188
+
189
+ def connection
190
+ return @connection if @connection && !@connection.closed?
191
+ @connection = Net::SSH.start(@node.host, @node.user, options_for_ssh)
192
+ end
193
+
194
+ def disconnect
195
+ @connection.close if @connection && !@connection.closed?
196
+ end
197
+
198
+ def sftp
199
+ @sftp ||= connection.sftp.connect
200
+ end
201
+
202
+ end
@@ -0,0 +1,86 @@
1
+ # CLI Group 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 groups'
9
+ command :group do |cgrp|
10
+
11
+ cgrp.desc 'Add an infrastructure group'
12
+ cgrp.arg_name 'GROUP'
13
+ cgrp.command :add do |cgrp_add|
14
+
15
+ cgrp_add.flag [:t, :target], :arg_name => 'NAME', :multiple => true, :desc => 'Add a known target (node or group) to this new group.'
16
+ cgrp_add.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the group.'
17
+
18
+ cgrp_add.action do |global,options,args|
19
+ raise ArgumentError, 'Missing or too many arguments!' unless args.size == 1
20
+ Inprovise::Controller.run(:add, options, :group, *args)
21
+ Inprovise::Controller.wait!
22
+ end
23
+
24
+ end
25
+
26
+ cgrp.desc 'Remove (an) infrastructure group(s)'
27
+ cgrp.arg_name 'GROUP[ GROUP [...]]'
28
+ cgrp.command :remove do |cgrp_del|
29
+
30
+ cgrp_del.action do |global,options,args|
31
+ raise ArgumentError, 'Missing argument!' if args.empty?
32
+ Inprovise::Controller.run(:remove, options, :group, *args)
33
+ Inprovise::Controller.wait!
34
+ end
35
+
36
+ end
37
+
38
+ cgrp.desc 'Update configuration for the given groups.'
39
+ cgrp.arg_name 'GROUP[ GROUP [...]]'
40
+ cgrp.command :update do |cgrp_update|
41
+
42
+ cgrp_update.flag [:c, :config], :arg_name => 'CFGKEY=CFGVAL', :multiple => true, :desc => 'Specify a configuration setting for the group(s)'
43
+ cgrp_update.switch [:r, :reset], negatable: false, :desc => 'Reset configuration before update (default is to merge updates)'
44
+ cgrp_update.flag [:t, :target], :arg_name => 'NAME', :multiple => true, :desc => 'Add a known target (node or group) to the group(s)'
45
+
46
+ cgrp_update.action do |global,options,args|
47
+ raise ArgumentError, 'Missing argument!' if args.empty?
48
+ Inprovise::Controller.run(:update, options, :group, *args)
49
+ Inprovise::Controller.wait!
50
+ end
51
+ end
52
+
53
+ cgrp.desc 'List infrastructure groups (all or specified group(s))'
54
+ cgrp.arg_name '[GROUP[ GROUP [...]]]'
55
+ cgrp.command :list do |cgrp_list|
56
+ cgrp_list.switch [:d, :details], negatable: false, :desc => 'Show group details'
57
+
58
+ cgrp_list.action do |global_options,options,args|
59
+ $stdout.puts " INFRASTRUCTURE GROUPS"
60
+ $stdout.puts " ====================="
61
+ if args.empty?
62
+ Inprovise::Infrastructure.list(Inprovise::Infrastructure::Group).each do |g|
63
+ Inprovise::Cli.show_target(g, options[:details])
64
+ end
65
+ else
66
+ args.each do |a|
67
+ tgt = Inprovise::Infrastructure.find(a)
68
+ case tgt
69
+ when Inprovise::Infrastructure::Node
70
+ $stdout.puts "ERROR: #{a} is not a group".red
71
+ when Inprovise::Infrastructure::Group
72
+ Inprovise::Cli.show_target(tgt, options[:details])
73
+ else
74
+ $stdout.puts "ERROR: #{a} is unknown".red
75
+ end
76
+ end
77
+ end
78
+ $stdout.puts
79
+ end
80
+ end
81
+
82
+ cgrp.default_command :list
83
+
84
+ end
85
+
86
+ end