inprovise 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/.travis.yml +28 -0
- data/Gemfile +9 -0
- data/LICENSE +8 -0
- data/README.md +197 -0
- data/Rakefile.rb +9 -0
- data/bin/rig +5 -0
- data/inprovise.gemspec +22 -0
- data/lib/inprovise/channel/ssh.rb +202 -0
- data/lib/inprovise/cli/group.rb +86 -0
- data/lib/inprovise/cli/node.rb +95 -0
- data/lib/inprovise/cli/provision.rb +84 -0
- data/lib/inprovise/cli.rb +105 -0
- data/lib/inprovise/cmd_channel.rb +100 -0
- data/lib/inprovise/cmd_helper.rb +150 -0
- data/lib/inprovise/control.rb +326 -0
- data/lib/inprovise/execution_context.rb +277 -0
- data/lib/inprovise/group.rb +67 -0
- data/lib/inprovise/helper/cygwin.rb +43 -0
- data/lib/inprovise/helper/linux.rb +181 -0
- data/lib/inprovise/helper/windows.rb +123 -0
- data/lib/inprovise/infra.rb +122 -0
- data/lib/inprovise/local_file.rb +120 -0
- data/lib/inprovise/logger.rb +79 -0
- data/lib/inprovise/node.rb +271 -0
- data/lib/inprovise/remote_file.rb +128 -0
- data/lib/inprovise/resolver.rb +36 -0
- data/lib/inprovise/script.rb +175 -0
- data/lib/inprovise/script_index.rb +46 -0
- data/lib/inprovise/script_runner.rb +110 -0
- data/lib/inprovise/sniff.rb +46 -0
- data/lib/inprovise/sniffer/linux.rb +64 -0
- data/lib/inprovise/sniffer/platform.rb +46 -0
- data/lib/inprovise/sniffer/unknown.rb +11 -0
- data/lib/inprovise/sniffer/windows.rb +32 -0
- data/lib/inprovise/template/inprovise.rb.erb +92 -0
- data/lib/inprovise/template.rb +38 -0
- data/lib/inprovise/trigger_runner.rb +36 -0
- data/lib/inprovise/version.rb +10 -0
- data/lib/inprovise.rb +145 -0
- data/test/cli_test.rb +314 -0
- data/test/cli_test_helper.rb +19 -0
- data/test/dsl_test.rb +43 -0
- data/test/fixtures/example.txt +1 -0
- data/test/fixtures/include.rb +4 -0
- data/test/fixtures/inprovise.rb +1 -0
- data/test/fixtures/myscheme.rb +1 -0
- data/test/infra_test.rb +189 -0
- data/test/local_file_test.rb +64 -0
- data/test/remote_file_test.rb +106 -0
- data/test/resolver_test.rb +66 -0
- data/test/script_index_test.rb +53 -0
- data/test/script_test.rb +56 -0
- data/test/test_helper.rb +237 -0
- 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
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
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
data/bin/rig
ADDED
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
|