canals 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0af6d8d1f776054496a2e57bd383087ceb9d847
4
+ data.tar.gz: 84eef80c5fc95a038bb58361ecbe6a80676a1f8b
5
+ SHA512:
6
+ metadata.gz: 068e49ef7a7a43c035e9d6c4ee8815ad9a985639b5e0b50d290f52e4afb30a0c7531babd8ab7aa8d8a6ff1ac3ef11df3314209657f626518cb5128f5542b3e01
7
+ data.tar.gz: 3add6233e74d5debf21966b693f3c362fe660b25a13eaadff67ce8d4e51b74c676ecff8b84720979b92142aaca66a14caca615d6f3bed3205b86caeafd805df3
data/.gitignore ADDED
@@ -0,0 +1,38 @@
1
+ /pkg
2
+ .idea/*
3
+ *.iml
4
+ *.swp
5
+ *.DS_Store
6
+
7
+ *.gem
8
+ *.rbc
9
+ /.config
10
+ /coverage/
11
+ /InstalledFiles
12
+ /pkg/
13
+ /spec/reports/
14
+ /test/tmp/
15
+ /test/version_tmp/
16
+ /tmp/
17
+
18
+ ## Specific to RubyMotion:
19
+ .dat*
20
+ .repl_history
21
+ build/
22
+
23
+ ## Documentation cache and generated files:
24
+ /.yardoc/
25
+ /_yardoc/
26
+ /doc/
27
+ /rdoc/
28
+
29
+ ## Environment normalisation:
30
+ /.bundle/
31
+ /vendor/bundle
32
+ /lib/bundler/man/
33
+
34
+ # for a library or gem, you might want to ignore these files since the code is
35
+ # intended to run in multiple environments; otherwise, check them in:
36
+ Gemfile.lock
37
+ # .ruby-version
38
+ # .ruby-gemset
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use ruby@canals --create
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'rake'
5
+ gem 'pry'
6
+ end
7
+
8
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ido Abramovich
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Canals: Help manage SSH tunnels
2
+
3
+ Canals eases the process of creating and managing SSH tunnels.
4
+ Behind the scenes, Canals creates SSH tunnels using the standard OpenSSH library, but it helps with making the process more forward and by remembering those tunnels between usages.
5
+
6
+ ### Instalation
7
+
8
+ ```
9
+ gem install canals
10
+ canal setup wizard
11
+ ```
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'bundler'
2
+
3
+ Bundler::GemHelper.install_tasks
data/assets/canals.sh ADDED
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+
3
+ _canal_complete() {
4
+ local cur prev
5
+
6
+ COMPREPLY=()
7
+ cur=${COMP_WORDS[COMP_CWORD]}
8
+ prev=${COMP_WORDS[COMP_CWORD-1]}
9
+
10
+ # Setup the base level (everything after "canal")
11
+ if [ $COMP_CWORD -eq 1 ]; then
12
+ COMPREPLY=( $(compgen \
13
+ -W "create environment help repo restart session setup start stop" \
14
+ -- $cur) )
15
+ return 0
16
+ fi
17
+
18
+ # Setup the second level
19
+ if [ $COMP_CWORD -eq 2 ]; then
20
+ case "$prev" in
21
+ start|stop|restart)
22
+ COMPREPLY=( $(compgen \
23
+ -W "`canal list tunnels`" \
24
+ -- $cur) )
25
+ ;;
26
+ environment)
27
+ COMPREPLY=( $(compgen \
28
+ -W "create help show" \
29
+ -- $cur) )
30
+ ;;
31
+ session)
32
+ COMPREPLY=( $(compgen \
33
+ -W "help restart restore show stop" \
34
+ -- $cur) )
35
+ ;;
36
+ setup)
37
+ COMPREPLY=( $(compgen \
38
+ -W "completion help wizard" \
39
+ -- $cur) )
40
+ ;;
41
+ # Everything else
42
+ *)
43
+ COMPREPLY=( $(compgen \
44
+ -W "`canal list commands $prev`" \
45
+ -- $cur) )
46
+ ;;
47
+ esac
48
+ return 0
49
+ fi
50
+
51
+ return 0
52
+ }
53
+ complete -F _canal_complete canal
data/bin/canal ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Signal.trap('INT') { exit 1 }
4
+
5
+ # resolve bin path, ignoring symlinks
6
+ require 'pathname'
7
+ bin_file = Pathname.new(__FILE__).realpath
8
+
9
+ # add self to libpath
10
+ $LOAD_PATH.unshift File.expand_path("../../lib", bin_file)
11
+
12
+ require "canals/cli"
13
+
14
+ Canals::Cli::Application.start(ARGV)
data/canals.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+ require 'canals/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "canals"
6
+ s.version = Canals::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+
9
+ s.summary = "Eases the process of creating and managing SSH tunnels"
10
+ s.description = "A console utility to help build ssh tunnels and keep them up"
11
+ s.authors = ["Ido Abramovich"]
12
+ s.email = "canals.sf17@abramovich.info"
13
+ s.homepage = 'http://github.com/idoa01/canals'
14
+
15
+ s.required_ruby_version = '>= 2.2.0'
16
+ s.rubyforge_project = "canals"
17
+ s.files = `git ls-files`.split($/)
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split($/)
19
+ s.executables = `git ls-files -- bin/*`.split($/).map{ |f| File.basename(f) }
20
+ s.require_path = "lib"
21
+ s.license = "MIT"
22
+
23
+ s.add_dependency 'thor', '~> 0.19.1'
24
+ s.add_dependency 'terminal-table', '~> 1.5'
25
+
26
+ s.add_development_dependency 'rspec', '~> 3.4'
27
+ end
@@ -0,0 +1,44 @@
1
+ require 'canals'
2
+ require 'canals/options'
3
+ require 'canals/environment'
4
+ require 'thor'
5
+
6
+
7
+ module Canals
8
+ module Cli
9
+ class Environment < Thor
10
+ include Thor::Actions
11
+
12
+ desc 'create NAME HOSTNAME', 'Create a new ssh environment'
13
+ method_option :user, :type => :string, :desc => "The user for the ssh proxy host"
14
+ method_option :pem, :type => :string, :desc => "The PEM file location for this environment"
15
+ method_option :default, :type => :boolean, :desc => "Make this the default enviroment"
16
+ def create(name, hostname)
17
+ user, host = hostname.split("@")
18
+ if host.nil?
19
+ host = hostname
20
+ user = nil
21
+ end
22
+ opts = {"name" => name, "hostname" => host}.merge(options)
23
+ opts["user"] = user if !user.nil?
24
+ env = Canals::Environment.new(opts)
25
+ Canals.repository.add_environment(env)
26
+ end
27
+
28
+ desc "show [ENV]", "Show the available tunnels"
29
+ def show(env=nil)
30
+ if Canals.environments.empty?
31
+ say "No environments currently defined."
32
+ return
33
+ end
34
+ require 'terminal-table'
35
+ rows = Canals.environments.select{ |e| env.nil? || e.name == env}.map{ |e| [(e.is_default? ? "*" : ""), e.name, e.user, e.hostname, e.pem] }
36
+ table = Terminal::Table.new :headings => ['', 'Name', 'User', 'Hostname', 'PEM'], :rows => rows
37
+ say table
38
+ end
39
+
40
+ default_task :show
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,38 @@
1
+ require 'canals'
2
+ require 'canals/tools/completion'
3
+
4
+ module Canals
5
+ module Cli
6
+ module Helpers
7
+
8
+ def tstop(name)
9
+ Canals.stop(name)
10
+ say "Tunnel #{name.inspect} stopped."
11
+ end
12
+
13
+ def tstart(name)
14
+ pid = Canals.start(name)
15
+ say "Created tunnel #{name.inspect} with pid #{pid}"
16
+ pid
17
+ end
18
+
19
+ def trestart(name)
20
+ tstop(name)
21
+ tstart(name)
22
+ end
23
+
24
+ def startup_checks
25
+ check_completion
26
+ end
27
+
28
+ def check_completion
29
+ if Canals.config[:completion_version]
30
+ if Canals.config[:completion_version] != Canals::VERSION
31
+ Canals::Tools::Completion.update_completion
32
+ say "bash completion script upgraded, use `source #{Canals::Tools::Completion.cmp_file}` to reload it", :red
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ require 'canals'
2
+ require 'canals/options'
3
+ require 'canals/environment'
4
+ require 'thor'
5
+ require 'pry'
6
+
7
+ module Canals
8
+ module Cli
9
+ class List < Thor
10
+ include Thor::Actions
11
+
12
+ desc 'environments', 'List the different environments'
13
+ def environments
14
+ envs = Canals.environments.map{ |conf| conf.name }
15
+ say envs.sort.join " "
16
+ end
17
+
18
+ desc 'tunnels', 'List the different tunnels'
19
+ def tunnels
20
+ tunnels = Canals.repository.map{ |conf| conf.name }
21
+ say tunnels.sort.join " "
22
+ end
23
+
24
+ desc 'commands', 'List all the base level commands'
25
+ def commands(subcommand=nil)
26
+ #binding.pry
27
+ thor_class = Canals::Cli::Application
28
+ if !subcommand.nil?
29
+ thor_class = thor_class.subcommand_classes[subcommand]
30
+ if thor_class.nil?
31
+ return
32
+ end
33
+ end
34
+
35
+ cmds = thor_class.all_commands.values.select{ |c| c.class == Thor::Command }.map{ |c| c.name }
36
+ say cmds.sort.join " "
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,75 @@
1
+ require 'canals'
2
+ require 'canals/cli/helpers'
3
+ require 'thor'
4
+
5
+
6
+ module Canals
7
+ module Cli
8
+ class Session < Thor
9
+ include Thor::Actions
10
+ include Canals::Cli::Helpers
11
+
12
+ desc "show", "Show the current session"
13
+ def show
14
+ return if session_empty?
15
+ require 'terminal-table'
16
+ require 'canals/core_ext/string'
17
+ columns = ["pid", "name", "socket"]
18
+ rows = Canals.session.map{ |s| columns.map{ |c| s[c.to_sym] } }
19
+ table = Terminal::Table.new :headings => columns.map{|c| c.sub("_"," ").titleize }, :rows => rows
20
+ say table
21
+ end
22
+
23
+ desc "restore", "Restore the connection to tunnels which aren't working"
24
+ def restore
25
+ on_all_canals_in_session(:restore) do |name|
26
+ if Canals.isalive? name
27
+ say "Canal #{name.inspect} is running."
28
+ else
29
+ Canals.session.del(name)
30
+ tstart(name)
31
+ end
32
+ end
33
+ end
34
+
35
+ desc "restart", "Restart the current session (closing and starting all connections)"
36
+ def restart
37
+ on_all_canals_in_session(:restart) do |name|
38
+ trestart(name)
39
+ end
40
+ end
41
+
42
+ desc "stop", "Stop the current session"
43
+ def stop
44
+ on_all_canals_in_session(:stop) do |name|
45
+ tstop(name)
46
+ end
47
+ end
48
+
49
+ no_commands do
50
+ def on_all_canals_in_session(command, &block)
51
+ return if session_empty?
52
+ Canals.session.map{|s| s[:name]}.each do |name|
53
+ say "#{command.to_s.capitalize} canal #{name.inspect}:", :green
54
+ block.call(name)
55
+ end
56
+ say
57
+ say "#{command} done.", :green
58
+ end
59
+
60
+ def session_empty?
61
+ if Canals.session.empty?
62
+ say "Canal session is currently empty."
63
+ true
64
+ else
65
+ false
66
+ end
67
+ end
68
+ end
69
+
70
+ default_task :show
71
+ end
72
+ end
73
+ end
74
+
75
+
@@ -0,0 +1,98 @@
1
+ require 'canals'
2
+ require 'canals/tools/completion'
3
+ require 'thor'
4
+
5
+ module Canals
6
+ module Cli
7
+ class Setup < Thor
8
+ include Thor::Actions
9
+
10
+ desc "wizard", "Run the setup wizard"
11
+ def wizard
12
+ say "Welcome to the Canals Setup Wizard!", :green
13
+ setup_first_environment
14
+ setup_first_tunnel
15
+ check_install_completion
16
+ say "Setup complete. Thanks! :)", :green
17
+ end
18
+
19
+ desc "completion", "Setup bash completion"
20
+ def completion
21
+ install_completion
22
+ end
23
+
24
+ no_commands do
25
+ def setup_first_environment
26
+ say "We'll start by setting up your first environment", :green
27
+ say "An 'environment' is the server you connect your tunnels through. you can have many environments."
28
+ say "The first environment is the default one used for new connections (but you can always change this default in the future"
29
+ say ""
30
+ return unless yes? "Wait, should we setup your first environment?", :green
31
+ opts = {}
32
+ opts["name"] = ask " Name for environment:"
33
+ opts["hostname"] = ask " Hostname:"
34
+ opts["user"] = ask " User:"
35
+ opts["pem"] = ask " PEM file:", :path => true
36
+ opts["default"] = true
37
+ opts.delete_if { |key, value| value.to_s.strip.empty? }
38
+ if opts["name"].nil? || opts["hostname"].nil?
39
+ say "couldn't add first environment, use 'canal environment create ...' to add it manually.", :red
40
+ return
41
+ end
42
+ env = Canals::Environment.new(opts)
43
+ Canals.repository.add_environment(env)
44
+ say "Environment added. To add more environments in the future, use 'canal environment create...'", :green
45
+ end
46
+
47
+ def setup_first_tunnel
48
+ return unless yes? "Should we setup a first tunnel?", :green
49
+
50
+ opts = {}
51
+ shell.padding += 1
52
+ opts["name"] = ask "Name for Tunnel:"
53
+ opts["remote_host"] = ask "Remote host:"
54
+ opts["remote_port"] = ask "Remote port:"
55
+ opts["local_port"] = ask "Local port:", :default =>opts["remote_port"]
56
+ use_env = !Canals.repository.environment.nil? && yes?("* Use stored environment?", :bold)
57
+ if use_env
58
+ opts["env"] = ask "Environment:", :default => Canals.repository.environment.name
59
+ else
60
+ opts["hostname"] = ask "Hostname:"
61
+ opts["user"] = ask "User:"
62
+ opts["pem"] = ask "PEM file:", :path => true
63
+ end
64
+ opts.delete_if { |key, value| value.to_s.strip.empty? }
65
+ shell.padding -= 1
66
+ opts = Canals::CanalOptions.new(opts)
67
+ Canals.create_tunnel(opts)
68
+ say "Tunnel added. To add more tunnels in the future, use 'canal create...'", :green
69
+ end
70
+
71
+ def check_install_completion
72
+ if !check(Canals::Tools::Completion.completion_installed?, "Checking if shell completion is installed...")
73
+ yes?('Shell completion not installed. Would you like to install it now? ') && install_completion
74
+ end
75
+ end
76
+
77
+ def install_completion
78
+ Canals::Tools::Completion.install_completion
79
+ say "Shell completion installed.", :green
80
+ end
81
+
82
+ def check(check_result, message)
83
+ spaces = " " * (shell.padding + 1)
84
+ say "#{spaces}#{message}#{spaces}#{humanize(check_result)}"
85
+ return check_result
86
+ end
87
+
88
+ def humanize(bool)
89
+ bool ? set_color("yes", :green) : set_color("no", :red)
90
+ end
91
+ end
92
+
93
+ default_task :wizard
94
+ end
95
+ end
96
+ end
97
+
98
+
data/lib/canals/cli.rb ADDED
@@ -0,0 +1,90 @@
1
+ require 'canals'
2
+ require 'canals/options'
3
+ require 'canals/environment'
4
+ require 'canals/cli/environment'
5
+ require 'canals/cli/session'
6
+ require 'canals/cli/helpers'
7
+ require 'canals/cli/setup'
8
+ require 'canals/cli/list'
9
+ require 'canals/core_ext/shell_colors'
10
+ require 'thor'
11
+
12
+ module Canals
13
+ module Cli
14
+ class Application < Thor
15
+ include Thor::Actions
16
+ include Canals::Cli::Helpers
17
+
18
+ def initialize(*args)
19
+ super
20
+ startup_checks
21
+ end
22
+
23
+ desc 'create NAME REMOTE_HOST REMOTE_PORT [LOCAL_PORT]', "Create a new tunnel; if LOCAL_PORT isn't supplied, REMOTE_PORT will be used as LOCAL"
24
+ method_option :env, :type => :string, :desc => "The proxy environment to use"
25
+ method_option :hostname, :type => :string, :desc => "The proxy host we will use to connect through"
26
+ method_option :user, :type => :string, :desc => "The user for the ssh proxy host"
27
+ def create(name, remote_host, remote_port, local_port=nil)
28
+ opts = {"name" => name, "remote_host" => remote_host, "remote_port" => remote_port, "local_port" => local_port}.merge(options)
29
+ opts = Canals::CanalOptions.new(opts)
30
+ Canals.create_tunnel(opts)
31
+ say "Tunnel #{name.inspect} created.", :green
32
+ end
33
+
34
+ desc 'start NAME', 'Start tunnel'
35
+ def start(name)
36
+ tstart(name)
37
+ end
38
+
39
+ desc 'stop NAME', 'Stop tunnel'
40
+ def stop(name)
41
+ tstop(name)
42
+ end
43
+
44
+ desc 'restart NAME', 'Restart tunnel'
45
+ def restart(name)
46
+ trestart(name)
47
+ end
48
+
49
+ desc "repo", "Show the available tunnels, for given enviroment if given"
50
+ method_option :full, :type => :boolean, :desc => "Show full data on repostitory"
51
+ method_option :env, :type => :string , :desc => "Show data only on given environment"
52
+ method_option :'sort-by', :type => :string, :desc => "Sort by this field", :default => "name"
53
+ def repo
54
+ if Canals.repository.empty?
55
+ say "Repository is currently empty."
56
+ return
57
+ end
58
+ require 'terminal-table'
59
+ require 'canals/core_ext/string'
60
+ columns = ["name", "remote_host", "remote_port", "local_port"]
61
+ columns_extra = ["bind_address", "env_name", "user", "hostname"]
62
+ if options[:full]
63
+ columns += columns_extra
64
+ end
65
+
66
+ env = options[:env]
67
+ sort_by = options[:'sort-by'].downcase.split.join("_").to_sym
68
+ rows = Canals.repository.select { |conf| env.nil? || conf.env_name == env }
69
+ .sort { |a,b| a.send(sort_by) <=> b.send(sort_by) rescue a.name <=> b.name }
70
+ .map { |conf| columns.map{ |c| conf.send c.to_sym } }
71
+ table = Terminal::Table.new :headings => columns.map{|c| c.sub("_"," ").titleize }, :rows => rows
72
+ say table
73
+ say "* use --full to show more data", [:white, :dim] if !options[:full]
74
+ end
75
+
76
+ desc "environment SUBCOMMAND", "Environment related command (use 'canal environment help' to find out more)"
77
+ subcommand "environment", Canals::Cli::Environment
78
+
79
+ desc "session SUBCOMMAND", "Session related commands (use 'canal session help' to find out more)"
80
+ subcommand "session", Canals::Cli::Session
81
+
82
+ desc "setup", "Setup related commands (use 'canal setup help' to find out more)"
83
+ subcommand "setup", Canals::Cli::Setup
84
+
85
+ desc "list", "hidden lists for autocompletion", :hide => true
86
+ subcommand "list", Canals::Cli::List
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,37 @@
1
+ require 'psych'
2
+ require 'pathname'
3
+ require 'forwardable'
4
+
5
+ module Canals
6
+ class Config
7
+ extend Forwardable
8
+
9
+ def initialize(root = nil)
10
+ @root = root
11
+ @config = load_config(global_config_file)
12
+ end
13
+
14
+ def_delegators :@config, :[], :[]=
15
+
16
+ def load_config(config_file)
17
+ valid_file = config_file && config_file.exist? && !config_file.size.zero?
18
+ return {} if !valid_file
19
+ return Psych.load_file(config_file)
20
+ end
21
+
22
+ def save!
23
+ FileUtils.mkdir_p(global_config_file.dirname)
24
+ File.open(global_config_file, 'w') do |file|
25
+ file.write(Psych.dump(@config))
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def global_config_file
32
+ file = File.join(Dir.home, '.canals/config')
33
+ Pathname.new(file)
34
+ end
35
+
36
+ end
37
+ end