rtprov 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +126 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +41 -0
- data/LICENSE.txt +21 -0
- data/README.md +96 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rtprov +4 -0
- data/lib/rtprov.rb +13 -0
- data/lib/rtprov/cli.rb +83 -0
- data/lib/rtprov/encryption.rb +38 -0
- data/lib/rtprov/initializer.rb +76 -0
- data/lib/rtprov/router.rb +76 -0
- data/lib/rtprov/session.rb +93 -0
- data/lib/rtprov/sftp.rb +37 -0
- data/lib/rtprov/template.rb +58 -0
- data/lib/rtprov/version.rb +3 -0
- data/rtprov.gemspec +46 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a398e411bb4dad6a5a9d9729fc8c9c7c6c88620836d08eb4edfef767943eea82
|
4
|
+
data.tar.gz: 6e475a17877c5bab3b6dff77a347582e0ac5d727684584ace3d9b0ad25e5e517
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 45e51ed23305e428916884a2c8dd0512332f1deed16edb25de19ba06e50e62e4be8c25e8e1a21929b84049df6ff6e43e41c221607d5cedc6f7046bef412024bc
|
7
|
+
data.tar.gz: b1580e8ea6d1580a544c405bbac4f150813492bade5f2f84ca7763a5ec86e1aec0a115651c5346889b78930010ddfdb0883e6fd3f154cd7042205b1abf28dfcd
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- bin/*
|
4
|
+
- node_modules/**/*
|
5
|
+
|
6
|
+
Layout/CaseIndentation:
|
7
|
+
EnforcedStyle: end
|
8
|
+
|
9
|
+
Layout/EndAlignment:
|
10
|
+
EnforcedStyleAlignWith: variable
|
11
|
+
|
12
|
+
Layout/EmptyLinesAroundAccessModifier:
|
13
|
+
EnforcedStyle: only_before
|
14
|
+
|
15
|
+
Layout/SpaceInLambdaLiteral:
|
16
|
+
EnforcedStyle: require_space
|
17
|
+
|
18
|
+
Layout/SpaceInsideBlockBraces:
|
19
|
+
SpaceBeforeBlockParameters: false
|
20
|
+
|
21
|
+
Layout/SpaceInsideHashLiteralBraces:
|
22
|
+
EnforcedStyle: no_space
|
23
|
+
|
24
|
+
Metrics/AbcSize:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Metrics/BlockLength:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/ClassLength:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Metrics/CyclomaticComplexity:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Metrics/LineLength:
|
37
|
+
Enabled: false
|
38
|
+
|
39
|
+
Metrics/MethodLength:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
Metrics/ModuleLength:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Metrics/PerceivedComplexity:
|
46
|
+
Enabled: false
|
47
|
+
|
48
|
+
Naming/HeredocDelimiterNaming:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
Naming/PredicateName:
|
52
|
+
Enabled: false
|
53
|
+
|
54
|
+
Naming/UncommunicativeMethodParamName:
|
55
|
+
Enabled: false
|
56
|
+
|
57
|
+
Style/Alias:
|
58
|
+
EnforcedStyle: prefer_alias_method
|
59
|
+
|
60
|
+
Style/AsciiComments:
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
Style/BlockDelimiters:
|
64
|
+
Enabled: false
|
65
|
+
|
66
|
+
Style/Documentation:
|
67
|
+
Enabled: false
|
68
|
+
|
69
|
+
Style/EmptyCaseCondition:
|
70
|
+
Enabled: false
|
71
|
+
|
72
|
+
Style/EmptyMethod:
|
73
|
+
EnforcedStyle: expanded
|
74
|
+
|
75
|
+
Style/FrozenStringLiteralComment:
|
76
|
+
Enabled: false
|
77
|
+
|
78
|
+
Style/HashSyntax:
|
79
|
+
Exclude:
|
80
|
+
- Rakefile
|
81
|
+
- "**/*.rake"
|
82
|
+
|
83
|
+
Style/Lambda:
|
84
|
+
EnforcedStyle: literal
|
85
|
+
|
86
|
+
Style/IfUnlessModifier:
|
87
|
+
Enabled: false
|
88
|
+
|
89
|
+
Style/MultilineBlockChain:
|
90
|
+
Enabled: false
|
91
|
+
|
92
|
+
Style/NumericLiterals:
|
93
|
+
Enabled: false
|
94
|
+
|
95
|
+
Style/NumericPredicate:
|
96
|
+
Enabled: false
|
97
|
+
|
98
|
+
Style/PercentLiteralDelimiters:
|
99
|
+
PreferredDelimiters:
|
100
|
+
'%i': '()'
|
101
|
+
'%w': '()'
|
102
|
+
'%r': '()'
|
103
|
+
|
104
|
+
Style/SpecialGlobalVars:
|
105
|
+
Enabled: false
|
106
|
+
|
107
|
+
Style/StringLiterals:
|
108
|
+
EnforcedStyle: double_quotes
|
109
|
+
|
110
|
+
Style/StringLiteralsInInterpolation:
|
111
|
+
EnforcedStyle: double_quotes
|
112
|
+
|
113
|
+
Style/SymbolArray:
|
114
|
+
Enabled: false
|
115
|
+
|
116
|
+
Style/TrailingCommaInArguments:
|
117
|
+
EnforcedStyleForMultiline: consistent_comma
|
118
|
+
|
119
|
+
Style/TrailingCommaInArrayLiteral:
|
120
|
+
EnforcedStyleForMultiline: consistent_comma
|
121
|
+
|
122
|
+
Style/TrailingCommaInHashLiteral:
|
123
|
+
EnforcedStyleForMultiline: consistent_comma
|
124
|
+
|
125
|
+
Style/ZeroLengthPredicate:
|
126
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rtprov (0.1.0)
|
5
|
+
reversible_cryptography
|
6
|
+
thor
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.0)
|
12
|
+
jaro_winkler (1.5.3)
|
13
|
+
parallel (1.17.0)
|
14
|
+
parser (2.6.3.0)
|
15
|
+
ast (~> 2.4.0)
|
16
|
+
rainbow (3.0.0)
|
17
|
+
rake (10.5.0)
|
18
|
+
reversible_cryptography (0.5.0)
|
19
|
+
thor
|
20
|
+
rubocop (0.74.0)
|
21
|
+
jaro_winkler (~> 1.5.1)
|
22
|
+
parallel (~> 1.10)
|
23
|
+
parser (>= 2.6)
|
24
|
+
rainbow (>= 2.2.2, < 4.0)
|
25
|
+
ruby-progressbar (~> 1.7)
|
26
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
27
|
+
ruby-progressbar (1.10.1)
|
28
|
+
thor (0.20.3)
|
29
|
+
unicode-display_width (1.6.0)
|
30
|
+
|
31
|
+
PLATFORMS
|
32
|
+
ruby
|
33
|
+
|
34
|
+
DEPENDENCIES
|
35
|
+
bundler (~> 2.0)
|
36
|
+
rake (~> 10.0)
|
37
|
+
rtprov!
|
38
|
+
rubocop (~> 0.74.0)
|
39
|
+
|
40
|
+
BUNDLED WITH
|
41
|
+
2.0.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 labocho
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# rtprov
|
2
|
+
|
3
|
+
Yamaha router (RTX or NVR series etc.) provisioning tool.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install rtprov
|
8
|
+
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
rtprov requires following commands. Please install these.
|
12
|
+
|
13
|
+
* colordiff (or diff)
|
14
|
+
* lftp
|
15
|
+
* ssh
|
16
|
+
* which
|
17
|
+
|
18
|
+
## Preparation
|
19
|
+
|
20
|
+
* Enable SSH and SFTP access.
|
21
|
+
* Create user and allow SSH access and administration.
|
22
|
+
|
23
|
+
And you must know following passwords.
|
24
|
+
|
25
|
+
* User password
|
26
|
+
* Administrator password
|
27
|
+
* Anonymous user password
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
### rtprov new
|
32
|
+
|
33
|
+
`rtprov new` creates directory and some files.
|
34
|
+
Please run other commands in created directory.
|
35
|
+
|
36
|
+
It creates `./encryption_key` to encrypt router configurations.
|
37
|
+
.gitignore ignores this. Please store it securely.
|
38
|
+
|
39
|
+
$ rtprov new my_office
|
40
|
+
|
41
|
+
|
42
|
+
### rtprov edit
|
43
|
+
|
44
|
+
`rtprov edit` creates or edits router configuration file in `./routers` directory. It is encrypted by `./encryption_key` file.
|
45
|
+
rtprov launches `ENV["RTPROV_EDITOR"]` or `ENV["EDITOR"]` to edit file.
|
46
|
+
|
47
|
+
# Launch editor and create/update routers/my_router.yml.enc
|
48
|
+
$ rtprov edit my_router
|
49
|
+
|
50
|
+
|
51
|
+
### rtprov show
|
52
|
+
|
53
|
+
`rtprov show` print router configuration to stdout.
|
54
|
+
|
55
|
+
# Prints decrypted routers/my_router.yml.enc
|
56
|
+
$ rtprov show my_router
|
57
|
+
|
58
|
+
### rtprov get
|
59
|
+
|
60
|
+
`rtprov get` gets config file from router and print to stdout.
|
61
|
+
If `-n, --number` option is not specified, it gets `config0`.
|
62
|
+
|
63
|
+
# Get config0 from my_router and print to stdout
|
64
|
+
$ rtprov get my_router
|
65
|
+
# Get config1 from my_router and print to stdout
|
66
|
+
$ rtprov get --number 1 my_router
|
67
|
+
|
68
|
+
|
69
|
+
### rtprov put
|
70
|
+
|
71
|
+
`rtprov put` puts config file to router and load it.
|
72
|
+
Second argument is template name. If you create `templates/my_config.erb`, template name is `my_config`.
|
73
|
+
If `-n, --number` option is not specified, it gets `config0`.
|
74
|
+
|
75
|
+
It prints diff before transfer and ask you.
|
76
|
+
rtprov uses `ENV["RTPROV_DIFF"]` or `colordiff` or `diff` to print diff.
|
77
|
+
|
78
|
+
# Rendering templates/my_config.erb and put to my_route as config0 and load it.
|
79
|
+
$ rtprov put my_router my_config
|
80
|
+
# Rendering templates/my_config.erb and put to my_route as config1 and load it.
|
81
|
+
$ rtprov put my_router --number 1 my_config
|
82
|
+
|
83
|
+
|
84
|
+
## Development
|
85
|
+
|
86
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
87
|
+
|
88
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/labocho/rtprov.
|
93
|
+
|
94
|
+
## License
|
95
|
+
|
96
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rtprov"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/exe/rtprov
ADDED
data/lib/rtprov.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rtprov/version"
|
2
|
+
|
3
|
+
module Rtprov
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
require_relative "rtprov/cli"
|
7
|
+
require_relative "rtprov/encryption"
|
8
|
+
require_relative "rtprov/initializer"
|
9
|
+
require_relative "rtprov/router"
|
10
|
+
require_relative "rtprov/session"
|
11
|
+
require_relative "rtprov/sftp"
|
12
|
+
require_relative "rtprov/template"
|
13
|
+
end
|
data/lib/rtprov/cli.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
module Rtprov
|
5
|
+
class CLI < ::Thor
|
6
|
+
desc "new REPO", "Create new rtprov repository"
|
7
|
+
def new(name)
|
8
|
+
Initializer.run(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "edit ROUTER", "Edit router config"
|
12
|
+
def edit(router_name)
|
13
|
+
Router.edit(router_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "show ROUTER", "Show router config"
|
17
|
+
def show(router_name)
|
18
|
+
puts Router.decrypt(router_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "get ROUTER", "Get config from router"
|
22
|
+
option :number, type: :numeric, default: 0, aliases: :n, desc: "Configuration number"
|
23
|
+
def get(router_name)
|
24
|
+
router = Router.load(router_name)
|
25
|
+
sftp = Sftp.new(router.host, router.user, router.administrator_password)
|
26
|
+
puts sftp.get("/system/config#{options[:number]}")
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "put ROUTER TEMPLATE", "Put config from router"
|
30
|
+
option :number, type: :numeric, default: 0, aliases: :n, desc: "Configuration number"
|
31
|
+
option :force, type: :boolean, default: false, aliases: :f, desc: "Don't ask to trasfer and load config"
|
32
|
+
def put(router_name, template_name)
|
33
|
+
current_file = "/system/config#{options[:number]}"
|
34
|
+
router = Router.load(router_name)
|
35
|
+
|
36
|
+
template = Template.find(router_name, template_name)
|
37
|
+
new_config = template.render(router.variables)
|
38
|
+
|
39
|
+
sftp = Sftp.new(router.host, router.user, router.administrator_password)
|
40
|
+
current_config = sftp.get(current_file)
|
41
|
+
diff = ENV["RTPROV_DIFF"] || %w(colordiff diff).find {|cmd| system("which", cmd, out: "/dev/null", err: "/dev/null") }
|
42
|
+
|
43
|
+
Dir.mktmpdir do |dir|
|
44
|
+
Dir.chdir(dir) do
|
45
|
+
File.write("new.conf", new_config.gsub(/^#.*$/, "").gsub(/(\r\n|\r|\n)+/, "\r\n"))
|
46
|
+
File.write("current.conf", current_config.gsub(/^#.*$/, "").gsub(/(\r\n|\r|\n)+/, "\r\n"))
|
47
|
+
system("#{diff} -u current.conf new.conf", out: $stdout, err: $stderr)
|
48
|
+
|
49
|
+
unless options[:force]
|
50
|
+
loop do
|
51
|
+
print "apply? (y/n): "
|
52
|
+
case $stdin.gets.strip
|
53
|
+
when "y"
|
54
|
+
break
|
55
|
+
when "n"
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
sftp.put("new.conf", current_file)
|
62
|
+
Session.start(router) do |s|
|
63
|
+
s.exec_with_passwords "load config #{options[:number]} silent no-key-generate"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "ls", "List routers"
|
70
|
+
def ls
|
71
|
+
Router.names.each do |name|
|
72
|
+
puts name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "ssh ROUTER", "exec ssh to router"
|
77
|
+
def ssh(router_name)
|
78
|
+
router = Router.load(router_name)
|
79
|
+
warn "Password: #{router.password}"
|
80
|
+
exec "ssh", "#{router.user}@#{router.host}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "reversible_cryptography"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module Rtprov
|
5
|
+
class Encryption
|
6
|
+
KEY_FILE = "encryption_key".freeze
|
7
|
+
|
8
|
+
attr_reader :key
|
9
|
+
|
10
|
+
def self.load_key
|
11
|
+
ENV["ENCRYPTION_KEY"] || (File.exist?(KEY_FILE) && File.read(KEY_FILE).strip) || raise("ENCRYPTION_KEY env or encryption_key file not found")
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.encrypt(plain, key = load_key)
|
15
|
+
new(key).encrypt(plain)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.decrypt(encrypted, key = load_key)
|
19
|
+
new(key).decrypt(encrypted)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.generate_key
|
23
|
+
SecureRandom.base64(512)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(key)
|
27
|
+
@key = key.dup.freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
def encrypt(plain)
|
31
|
+
ReversibleCryptography::Message.encrypt(plain, key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def decrypt(encrypted)
|
35
|
+
ReversibleCryptography::Message.decrypt(encrypted, key)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "open3"
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
module Rtprov
|
6
|
+
class Initializer
|
7
|
+
include FileUtils::Verbose
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def self.run(name)
|
12
|
+
new(name).run
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(name)
|
16
|
+
@name = name.dup.freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
raise "Already exists #{name} directory" if File.exist?(name)
|
21
|
+
|
22
|
+
mkdir name
|
23
|
+
Dir.chdir(name) do
|
24
|
+
mkdir "routers"
|
25
|
+
touch "routers/.keep"
|
26
|
+
|
27
|
+
mkdir "templates"
|
28
|
+
touch "templates/.keep"
|
29
|
+
|
30
|
+
puts "Create encryption_key"
|
31
|
+
key = Encryption.generate_key
|
32
|
+
File.write("encryption_key", key)
|
33
|
+
|
34
|
+
exec "bundle init"
|
35
|
+
exec "bundle add rtprov -v #{VERSION}"
|
36
|
+
exec "bundle binstubs rtprov"
|
37
|
+
|
38
|
+
puts "Create .gitignore"
|
39
|
+
gitignore = <<~EOS
|
40
|
+
/encryption_key
|
41
|
+
EOS
|
42
|
+
File.write(".gitignore", gitignore)
|
43
|
+
|
44
|
+
exec "git init"
|
45
|
+
exec "git add ."
|
46
|
+
end
|
47
|
+
|
48
|
+
puts <<~EOS
|
49
|
+
|
50
|
+
============================================================
|
51
|
+
!!! Please remember `encryption_key`. Git ignores it. !!!
|
52
|
+
============================================================
|
53
|
+
|
54
|
+
And do below.
|
55
|
+
1. cd #{name}
|
56
|
+
2. bin/rtprov edit YOUR_ROUTER_NAME
|
57
|
+
3. bin/rtprov get YOUR_ROUTER_NAME > tempaltes/config.erb
|
58
|
+
4. Extract credentials in templates/config.erb
|
59
|
+
EOS
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def exec(cmd, *args)
|
64
|
+
puts "#{cmd} #{args.shelljoin}"
|
65
|
+
|
66
|
+
o, e, s = Open3.capture3(cmd, *args)
|
67
|
+
|
68
|
+
unless s.success?
|
69
|
+
warn e
|
70
|
+
raise "`#{cmd} #{args.shelljoin}` failed"
|
71
|
+
end
|
72
|
+
|
73
|
+
o
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
require "shellwords"
|
3
|
+
|
4
|
+
module Rtprov
|
5
|
+
class Router
|
6
|
+
ATTRIBUTES = %w(host user password administrator_password anonymous_password variables).map(&:freeze).freeze
|
7
|
+
|
8
|
+
attr_reader :name, *ATTRIBUTES
|
9
|
+
|
10
|
+
def self.edit(name)
|
11
|
+
encrypted_file = "routers/#{name}.yml.enc"
|
12
|
+
decrypted = if File.exist?(encrypted_file)
|
13
|
+
Encryption.decrypt(File.read(encrypted_file))
|
14
|
+
else
|
15
|
+
<<~YAML
|
16
|
+
host: 127.0.0.1
|
17
|
+
user: admin
|
18
|
+
password: opensesame
|
19
|
+
administrator_password: opensesame
|
20
|
+
anonymous_password: anonymous_password
|
21
|
+
variables: {}
|
22
|
+
YAML
|
23
|
+
end
|
24
|
+
|
25
|
+
Dir.mktmpdir do |dir|
|
26
|
+
temp = "#{dir}/#{name}.yml"
|
27
|
+
File.write(temp, decrypted)
|
28
|
+
|
29
|
+
if system("#{editor} #{temp.shellescape}", out: $stdout, err: $stderr)
|
30
|
+
encrypted = Encryption.encrypt(File.read(temp))
|
31
|
+
File.write(encrypted_file, encrypted)
|
32
|
+
warn "Saved to #{encrypted_file}"
|
33
|
+
else
|
34
|
+
warn "Not saved"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.editor
|
40
|
+
return ENV["RTPROV_EDITOR"] if ENV["RTPROV_EDITOR"]
|
41
|
+
|
42
|
+
# rubocop: disable Lint/HandleExceptions
|
43
|
+
begin
|
44
|
+
o, _e, s = Open3.capture3("git config core.editor")
|
45
|
+
return o.strip if s.success?
|
46
|
+
rescue Errno::ENOENT
|
47
|
+
end
|
48
|
+
# rubocop: enable Lint/HandleExceptions
|
49
|
+
|
50
|
+
ENV["EDITOR"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.decrypt(name)
|
54
|
+
Encryption.decrypt(File.read("routers/#{name}.yml.enc"))
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.names
|
58
|
+
Dir["routers/*.yml.enc"].map {|path|
|
59
|
+
File.basename(path).gsub(/\.yml\.enc\z/, "")
|
60
|
+
}.sort
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.load(name)
|
64
|
+
new(name, YAML.safe_load(decrypt(name)))
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(name, attributes)
|
68
|
+
@name = name
|
69
|
+
|
70
|
+
attributes.each do |k, v|
|
71
|
+
ATTRIBUTES.include?(k) || raise("Unknown attribute found `#{k}`")
|
72
|
+
instance_variable_set "@#{k}", v
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "expect"
|
2
|
+
require "pty"
|
3
|
+
require "shellwords"
|
4
|
+
|
5
|
+
module Rtprov
|
6
|
+
class Session
|
7
|
+
attr_reader :router, :reader, :writer, :prompt, :prompt_pattern
|
8
|
+
|
9
|
+
def self.start(router, &block)
|
10
|
+
cmd = [
|
11
|
+
"ssh",
|
12
|
+
"#{router.user}@#{router.host}",
|
13
|
+
].shelljoin
|
14
|
+
|
15
|
+
PTY.getpty(cmd) do |r, w, _pid|
|
16
|
+
w.sync = true
|
17
|
+
|
18
|
+
r.expect(/password/)
|
19
|
+
w.puts router.password
|
20
|
+
prompt = r.expect(/^(.*>) /)[1]
|
21
|
+
|
22
|
+
session = new(router, r, w, prompt)
|
23
|
+
session.exec("console character en.ascii")
|
24
|
+
session.exec("console lines infinity") # disable pager
|
25
|
+
session.exec("console columns 200")
|
26
|
+
|
27
|
+
session.as_administrator(&block)
|
28
|
+
|
29
|
+
w.puts "exit"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(router, reader, writer, prompt = ">")
|
34
|
+
@router = router
|
35
|
+
@reader = reader
|
36
|
+
@writer = writer
|
37
|
+
@prompt = prompt.dup.freeze
|
38
|
+
@prompt_pattern = Regexp.compile("^" + Regexp.escape(prompt) + " ").freeze
|
39
|
+
end
|
40
|
+
|
41
|
+
def exec(cmd)
|
42
|
+
writer.puts cmd
|
43
|
+
out, * = reader.expect(prompt_pattern)
|
44
|
+
|
45
|
+
unless out
|
46
|
+
raise "Command `#{cmd}` timed out"
|
47
|
+
end
|
48
|
+
|
49
|
+
out.each_line.to_a[1..-2].join # remove first line like '> cmd' and last line line '> '
|
50
|
+
end
|
51
|
+
|
52
|
+
def exec_with_passwords(cmd)
|
53
|
+
writer.puts cmd
|
54
|
+
|
55
|
+
reader.expect(/^Login Password: /)
|
56
|
+
writer.puts router.anonymous_password
|
57
|
+
|
58
|
+
reader.expect(/^Administrator Password: /)
|
59
|
+
writer.puts router.administrator_password
|
60
|
+
|
61
|
+
writer.puts "console prompt '#{prompt.gsub(/\#$/, "")}'" # load config may change prompt
|
62
|
+
out, * = reader.expect(prompt_pattern)
|
63
|
+
|
64
|
+
unless out
|
65
|
+
raise "Command `#{cmd}` timed out"
|
66
|
+
end
|
67
|
+
|
68
|
+
out.each_line.to_a[1..-2].join # remove first line like '> cmd' and last line line '> '
|
69
|
+
end
|
70
|
+
|
71
|
+
def as_administrator(&block)
|
72
|
+
writer.puts "administrator"
|
73
|
+
reader.expect(/Password: /)
|
74
|
+
writer.puts router.administrator_password
|
75
|
+
reader.expect(/^.*# /)
|
76
|
+
|
77
|
+
begin
|
78
|
+
# set new prompt because default administrator prompt "# " matches config file comment etc.
|
79
|
+
session = self.class.new(router, reader, writer, "RTPROV#")
|
80
|
+
session.exec "console prompt RTPROV"
|
81
|
+
block.call(session)
|
82
|
+
ensure
|
83
|
+
writer.puts "console prompt '#{prompt.gsub(/>$/, "")}'"
|
84
|
+
reader.expect(/^.*# /)
|
85
|
+
end
|
86
|
+
|
87
|
+
writer.puts "exit"
|
88
|
+
reader.expect "Save new configuration ? (Y/N)"
|
89
|
+
writer.puts "Y"
|
90
|
+
reader.expect(prompt_pattern)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/rtprov/sftp.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "open3"
|
2
|
+
require "tmpdir"
|
3
|
+
|
4
|
+
module Rtprov
|
5
|
+
class Sftp
|
6
|
+
attr_reader :host, :user, :password
|
7
|
+
|
8
|
+
def initialize(host, user, password)
|
9
|
+
@host = host.dup.freeze
|
10
|
+
@user = user.dup.freeze
|
11
|
+
@password = password.dup.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def get(src)
|
15
|
+
Dir.mktmpdir do |dir|
|
16
|
+
dest = File.join(dir, File.basename(src))
|
17
|
+
run "get #{src} -o #{dest}"
|
18
|
+
File.read(dest)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def put(src, dest)
|
23
|
+
run "put #{src} -o #{dest}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def run(command)
|
28
|
+
# use lftp (instead of sftp) to specify user and password by arguments
|
29
|
+
o, e, s = Open3.capture3("lftp", "-u", "#{user},#{password}", "sftp://#{host}", stdin_data: command)
|
30
|
+
unless s.success?
|
31
|
+
raise "lftp command `#{command}` failed on sftp://#{host} by #{user}: #{e}"
|
32
|
+
end
|
33
|
+
|
34
|
+
o
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "erb"
|
2
|
+
|
3
|
+
module Rtprov
|
4
|
+
class Template
|
5
|
+
class RenderingContext
|
6
|
+
def initialize(local_vars)
|
7
|
+
local_vars.transform_values! {|v| hash_with_accessor(v) }
|
8
|
+
local_vars.each do |k, v|
|
9
|
+
binding.local_variable_set(k.to_sym, v)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def binding
|
14
|
+
@binding ||= super
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
# define key name method for hash recursively
|
19
|
+
def hash_with_accessor(obj)
|
20
|
+
case obj
|
21
|
+
when Array
|
22
|
+
obj.map {|e| hash_with_accessor(e) }
|
23
|
+
when Hash
|
24
|
+
obj.each_with_object({}) do |(k, v), h|
|
25
|
+
h[k] = hash_with_accessor(v)
|
26
|
+
h.singleton_class.define_method k do
|
27
|
+
fetch(k)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
obj
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find(router, name)
|
37
|
+
candidates = [
|
38
|
+
"templates/#{router}/#{name}.erb",
|
39
|
+
"templates/#{name}.erb",
|
40
|
+
]
|
41
|
+
|
42
|
+
candidates.find do |candidate|
|
43
|
+
return new(File.read(candidate)) if File.exist?(candidate)
|
44
|
+
end
|
45
|
+
|
46
|
+
raise "Template `#{name}` not found in #{candidates.inspect}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(source)
|
50
|
+
@erb = ERB.new(source, trim_mode: "-")
|
51
|
+
end
|
52
|
+
|
53
|
+
def render(variables)
|
54
|
+
context = RenderingContext.new(variables)
|
55
|
+
@erb.result(context.binding)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/rtprov.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "rtprov/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "rtprov"
|
9
|
+
spec.version = Rtprov::VERSION
|
10
|
+
spec.authors = ["labocho"]
|
11
|
+
spec.email = ["labocho@penguinlab.jp"]
|
12
|
+
|
13
|
+
spec.summary = "Yamaha router (RTX or NVR series) provisioning tool"
|
14
|
+
spec.description = "Yamaha router (RTX or NVR series) provisioning tool"
|
15
|
+
spec.homepage = "https://github.com/labocho/rtprov"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
19
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
20
|
+
if spec.respond_to?(:metadata)
|
21
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
22
|
+
|
23
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
24
|
+
spec.metadata["source_code_uri"] = "https://github.com/labocho/rtprov"
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/labocho/rtprov/master/CHANGELOG.md"
|
26
|
+
else
|
27
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
28
|
+
"public gem pushes."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Specify which files should be added to the gem when it is released.
|
32
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
33
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
34
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/}) }
|
35
|
+
end
|
36
|
+
spec.bindir = "exe"
|
37
|
+
spec.executables = spec.files.grep(%r(^exe/)) {|f| File.basename(f) }
|
38
|
+
spec.require_paths = ["lib"]
|
39
|
+
|
40
|
+
spec.add_dependency "reversible_cryptography"
|
41
|
+
spec.add_dependency "thor"
|
42
|
+
|
43
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
44
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
45
|
+
spec.add_development_dependency "rubocop", "~> 0.74.0"
|
46
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rtprov
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- labocho
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-09-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: reversible_cryptography
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.74.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.74.0
|
83
|
+
description: Yamaha router (RTX or NVR series) provisioning tool
|
84
|
+
email:
|
85
|
+
- labocho@penguinlab.jp
|
86
|
+
executables:
|
87
|
+
- rtprov
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- CHANGELOG.md
|
94
|
+
- Gemfile
|
95
|
+
- Gemfile.lock
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- bin/console
|
100
|
+
- bin/setup
|
101
|
+
- exe/rtprov
|
102
|
+
- lib/rtprov.rb
|
103
|
+
- lib/rtprov/cli.rb
|
104
|
+
- lib/rtprov/encryption.rb
|
105
|
+
- lib/rtprov/initializer.rb
|
106
|
+
- lib/rtprov/router.rb
|
107
|
+
- lib/rtprov/session.rb
|
108
|
+
- lib/rtprov/sftp.rb
|
109
|
+
- lib/rtprov/template.rb
|
110
|
+
- lib/rtprov/version.rb
|
111
|
+
- rtprov.gemspec
|
112
|
+
homepage: https://github.com/labocho/rtprov
|
113
|
+
licenses:
|
114
|
+
- MIT
|
115
|
+
metadata:
|
116
|
+
allowed_push_host: https://rubygems.org
|
117
|
+
homepage_uri: https://github.com/labocho/rtprov
|
118
|
+
source_code_uri: https://github.com/labocho/rtprov
|
119
|
+
changelog_uri: https://github.com/labocho/rtprov/master/CHANGELOG.md
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubygems_version: 3.0.3
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Yamaha router (RTX or NVR series) provisioning tool
|
139
|
+
test_files: []
|