canals 0.8.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 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