cabal 0.4.2 → 0.5.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 +4 -4
- data/.travis.yml +1 -0
- data/README.md +40 -20
- data/Rakefile +7 -0
- data/cabal.gemspec +2 -2
- data/exe/cabal +1 -1
- data/lib/cabal.rb +2 -1
- data/lib/cabal/cli.rb +34 -44
- data/lib/cabal/cli/authenticated.rb +27 -0
- data/lib/cabal/cli/key.rb +18 -0
- data/lib/cabal/cli/list.rb +24 -0
- data/lib/cabal/cli/main.rb +29 -0
- data/lib/cabal/cli/ssh.rb +46 -0
- data/lib/cabal/client.rb +39 -4
- data/lib/cabal/identity_manager.rb +19 -2
- data/lib/cabal/ssh.rb +65 -15
- data/lib/cabal/version.rb +1 -1
- metadata +13 -10
- data/lib/cabal/commands/key.rb +0 -32
- data/lib/cabal/commands/ssh.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54c08e765a34142602ac7933207bd41adb423cbf
|
4
|
+
data.tar.gz: d66b1d6dab67a5d5c91be6732a7e00551cf499ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6684c86512ddafaf29c7eb002dc87de2262f9c6e6a2b4bc3656cc3ba0a73acb4399485b0e8a17e8fb464300b56ff05ad4d4d2512ced8066b7d070ea142582060
|
7
|
+
data.tar.gz: e33cdbf4db6a8dc8fde6d86bb2093aa6c8e8e1b6596c96a8bebaaaee7f7a8655ba2a92a00b390db8a865eb492c03a3b5408258a2272ff13a3c21bd0830104be5
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -10,13 +10,13 @@ Cabal is a simple system for SSH key distribution and consumption. This is a CLI
|
|
10
10
|
|
11
11
|
## Usage ##
|
12
12
|
|
13
|
-
To get started, you'll need to create `.cabal.yml` in your user's home directory, and you'll need to provide at least the URL of your Cabal::API. If you plan to use the `ssh` command to connect to a server that has an authorized Cabal key, you'll also need to specify your access
|
13
|
+
To get started, you'll need to create `.cabal.yml` in your user's home directory, and you'll need to provide at least the URL of your Cabal::API. If you plan to use the `ssh` command to connect to a server that has an authorized Cabal key, you'll also need to specify your access token and secret token (provided by your Cabal::API administrator):
|
14
14
|
|
15
15
|
```yaml
|
16
16
|
---
|
17
17
|
:url: http://your-cabal-hostname/path/to/the/api
|
18
|
-
:access_key: your-access-
|
19
|
-
:secret_key: your-secret-
|
18
|
+
:access_key: your-access-token
|
19
|
+
:secret_key: your-secret-token
|
20
20
|
```
|
21
21
|
|
22
22
|
### Getting Help ###
|
@@ -24,38 +24,43 @@ To get started, you'll need to create `.cabal.yml` in your user's home directory
|
|
24
24
|
The `cabal` application provides some nicely-formatted help on the command line. To access the help, provided that you've created your `~/.cabal.yml`, run the following:
|
25
25
|
|
26
26
|
```
|
27
|
-
cabal help
|
27
|
+
cabal --help
|
28
28
|
```
|
29
29
|
|
30
30
|
That will output something like this:
|
31
31
|
|
32
32
|
```
|
33
33
|
NAME
|
34
|
-
|
34
|
+
cabal - An interface to the Cabal API
|
35
35
|
|
36
36
|
SYNOPSIS
|
37
|
-
|
37
|
+
cabal command
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
GLOBAL OPTIONS
|
43
|
-
--help - Show this message
|
44
|
-
--version - Display the program version
|
39
|
+
OPTIONS
|
40
|
+
-h, --help - Shows this message
|
45
41
|
|
46
42
|
COMMANDS
|
47
|
-
|
48
|
-
|
49
|
-
ssh - Connect to a node on a cluster
|
43
|
+
key - Get the public key for a cluster
|
44
|
+
ssh - Connect to a node on a cluster
|
50
45
|
```
|
51
46
|
|
52
|
-
To get help on a specific command, you can `cabal help
|
47
|
+
To get help on a specific command, you can `cabal command --help` (ie `cabal key --help`).
|
53
48
|
|
54
|
-
###
|
49
|
+
### Listing Known Clusters ###
|
50
|
+
|
51
|
+
***To learn more, check out `cabal list --help`***
|
52
|
+
|
53
|
+
This is a privileged call, so you need an access key and a secret key in your configuration file.
|
55
54
|
|
56
|
-
|
55
|
+
To get a list of the clusters that Cabal knows about, you'd do the following:
|
57
56
|
|
58
|
-
|
57
|
+
```
|
58
|
+
cabal list
|
59
|
+
```
|
60
|
+
|
61
|
+
### Getting A Public Key ###
|
62
|
+
|
63
|
+
***To learn more, check out `cabal key --help`***
|
59
64
|
|
60
65
|
To get a public key for a cluster named "totallyarealcluster," you'd do the following:
|
61
66
|
|
@@ -72,9 +77,20 @@ cabal key Totally a real clusteR
|
|
72
77
|
cabal key totallyarealcluster
|
73
78
|
```
|
74
79
|
|
80
|
+
This command is an alternative to hitting the API endpoint via curl (or similar) to grab a public key, which might be used when installing an authorized key on a server. The following example would append the key for "totallyarealcluster" to the current user's authorized_keys file if it wasn't already present:
|
81
|
+
|
82
|
+
```
|
83
|
+
if ! grep -q totallyarealcluster ~/.ssh/authorized_keys
|
84
|
+
then
|
85
|
+
cabal key totallyarealcluster >> ~/.ssh/authorized_keys
|
86
|
+
fi
|
87
|
+
```
|
88
|
+
|
89
|
+
This is an unprivileged call, so you won't actually need an access key or a secret key.
|
90
|
+
|
75
91
|
### Connecting To A Server ###
|
76
92
|
|
77
|
-
***To learn more, check out `cabal help
|
93
|
+
***To learn more, check out `cabal ssh --help`***
|
78
94
|
|
79
95
|
This is a privileged call, so you need an access key and a secret key in your configuration file.
|
80
96
|
|
@@ -91,6 +107,10 @@ This basically does the following:
|
|
91
107
|
* Connects you to the server, forwarding your ssh-agent
|
92
108
|
* Removes the key and **ALL** ssh-agent identities on disconnect
|
93
109
|
|
110
|
+
By default, the forwarded ssh-agent will only have access to the private key for 30 minutes. To change this, you can use the `--lifetime` option to specify a different lifetime for your session.
|
111
|
+
|
112
|
+
For tasks that may take a very long time (cluster upgrades, etc), you can pass a lifetime of `0` to keep the agent forwarding in place for the entirety of your session.
|
113
|
+
|
94
114
|
## Development ##
|
95
115
|
|
96
116
|
Branches and releases for this project are managed by [git-flow](https://github.com/nvie/gitflow).
|
data/Rakefile
CHANGED
data/cabal.gemspec
CHANGED
@@ -26,10 +26,10 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency 'factis', '~> 1.0'
|
27
27
|
spec.add_development_dependency 'simplecov', '~> 0.10'
|
28
28
|
spec.add_development_dependency 'webmock', '~> 1.22'
|
29
|
-
spec.add_development_dependency '
|
29
|
+
spec.add_development_dependency 'toady', '~> 0.0'
|
30
30
|
|
31
31
|
spec.add_runtime_dependency 'cabal-util', '~> 0.1'
|
32
|
-
spec.add_runtime_dependency '
|
32
|
+
spec.add_runtime_dependency 'belafonte', '~> 0.5'
|
33
33
|
spec.add_runtime_dependency 'oj', '~> 2.12'
|
34
34
|
spec.add_runtime_dependency 'faraday', '~> 0.9'
|
35
35
|
end
|
data/exe/cabal
CHANGED
data/lib/cabal.rb
CHANGED
data/lib/cabal/cli.rb
CHANGED
@@ -1,60 +1,50 @@
|
|
1
|
-
require 'gli'
|
2
1
|
require 'yaml'
|
3
2
|
require 'cabal'
|
4
|
-
require 'cabal/
|
5
|
-
require 'cabal/
|
3
|
+
require 'cabal/cli/ssh'
|
4
|
+
require 'cabal/cli/key'
|
5
|
+
require 'cabal/cli/list'
|
6
|
+
require 'cabal/cli/main'
|
6
7
|
|
7
8
|
module Cabal
|
8
|
-
|
9
|
-
|
9
|
+
# Bootstrap and configure the CLI application
|
10
|
+
module CLI
|
11
|
+
# The expected config file does not exist
|
12
|
+
NoConfig = Class.new(StandardError)
|
10
13
|
|
11
|
-
|
14
|
+
# The config file does not contain a :url
|
15
|
+
NoURLDefinition = Class.new(StandardError)
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def register_command(command)
|
18
|
-
command.bootstrap(self)
|
19
|
-
end
|
17
|
+
# The config file does not contain :access_key and :secret_key
|
18
|
+
NoPrivateConfig = Class.new(StandardError)
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
# Calculates the expected path of the Cabal config file
|
21
|
+
# @return [String] the config file path
|
22
|
+
def self.cabal_yml
|
23
|
+
File.expand_path(File.join(ENV['HOME'], '.cabal.yml'))
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
program_desc 'An interface to the Cabal API'
|
32
|
-
version Cabal::VERSION
|
33
|
-
subcommand_option_handling :normal
|
34
|
-
arguments :strict
|
35
|
-
|
36
|
-
pre do |global,command,options,args|
|
37
|
-
if File.exist?(cabal_yml)
|
38
|
-
config = YAML.load(File.read(cabal_yml))
|
39
|
-
global.merge!(config)
|
40
|
-
true
|
41
|
-
else
|
42
|
-
stderr.puts "Please create #{cabal_yml} and try again"
|
43
|
-
false
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
post do |global,command,options,args|
|
48
|
-
end
|
26
|
+
# Parses the config file and sets up the global configuration
|
27
|
+
# @return [nil]
|
28
|
+
def self.setup
|
29
|
+
if File.exist?(cabal_yml)
|
30
|
+
@config = YAML.load(File.read(cabal_yml))
|
49
31
|
|
50
|
-
|
51
|
-
|
32
|
+
raise NoURLDefinition.new("#{cabal_yml} must contain a :url: definition") unless config[:url]
|
33
|
+
else
|
34
|
+
raise NoConfig.new("Please create #{cabal_yml} and try again")
|
52
35
|
end
|
36
|
+
end
|
53
37
|
|
54
|
-
|
55
|
-
|
38
|
+
# The global config for the CLI
|
39
|
+
# @return [Hash] the current configuration
|
40
|
+
def self.config
|
41
|
+
@config ||= {}
|
42
|
+
end
|
56
43
|
|
57
|
-
|
44
|
+
# Clears the global configuration
|
45
|
+
# @return [nil]
|
46
|
+
def self.reset
|
47
|
+
@config = {}
|
58
48
|
end
|
59
49
|
end
|
60
50
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'belafonte'
|
2
|
+
require 'cabal/cli'
|
3
|
+
|
4
|
+
module Cabal
|
5
|
+
module CLI
|
6
|
+
module Authenticated
|
7
|
+
def with_authentication(config, &block)
|
8
|
+
begin
|
9
|
+
unless config[:access_key] && config[:secret_key]
|
10
|
+
raise Cabal::CLI::NoPrivateConfig
|
11
|
+
end
|
12
|
+
|
13
|
+
block.call
|
14
|
+
|
15
|
+
rescue Cabal::CLI::NoPrivateConfig
|
16
|
+
stderr.puts "You must have an access key and a secret key"
|
17
|
+
rescue Cabal::Client::UnauthorizedError
|
18
|
+
stderr.puts "You are not authorized to use the private API."
|
19
|
+
kernel.exit(1)
|
20
|
+
rescue Cabal::Client::NotFoundError
|
21
|
+
stderr.puts "The cluster '#{option(:cluster_name)}' does not exist."
|
22
|
+
kernel.exit(1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'belafonte'
|
2
|
+
require 'cabal/cli'
|
3
|
+
|
4
|
+
module Cabal
|
5
|
+
module CLI
|
6
|
+
class Key < Belafonte::App
|
7
|
+
title 'key'
|
8
|
+
summary 'Get the public key for a cluster'
|
9
|
+
|
10
|
+
arg :cluster_name,
|
11
|
+
times: :unlimited
|
12
|
+
|
13
|
+
def handle
|
14
|
+
stdout.puts partake(:client).public_key(arg(:cluster_name).join(' '))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'belafonte'
|
2
|
+
require 'cabal/cli'
|
3
|
+
require 'cabal/cli/authenticated'
|
4
|
+
|
5
|
+
module Cabal
|
6
|
+
module CLI
|
7
|
+
class List < Belafonte::App
|
8
|
+
include Authenticated
|
9
|
+
|
10
|
+
title 'list'
|
11
|
+
summary 'List all known clusters'
|
12
|
+
description "Displays the list of all known clusters, one line at a time"
|
13
|
+
|
14
|
+
arg :cluster_name,
|
15
|
+
times: :unlimited
|
16
|
+
|
17
|
+
def handle
|
18
|
+
with_authentication(partake(:config)) do
|
19
|
+
stdout.puts partake(:client).clusters.join("\n")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'cabal/cli/key'
|
2
|
+
require 'cabal/cli/list'
|
3
|
+
require 'cabal/cli/ssh'
|
4
|
+
|
5
|
+
module Cabal
|
6
|
+
module CLI
|
7
|
+
class Main < Belafonte::App
|
8
|
+
title 'cabal'
|
9
|
+
summary 'An interface to the Cabal API'
|
10
|
+
|
11
|
+
mount Cabal::CLI::Key
|
12
|
+
mount Cabal::CLI::List
|
13
|
+
mount Cabal::CLI::SSH
|
14
|
+
|
15
|
+
def setup
|
16
|
+
Cabal::CLI.setup
|
17
|
+
config = Cabal::CLI.config
|
18
|
+
client = Cabal::Client.new(
|
19
|
+
api_base: config[:url],
|
20
|
+
access_key: config[:access_key],
|
21
|
+
secret_key: config[:secret_key]
|
22
|
+
)
|
23
|
+
|
24
|
+
share(:config, config)
|
25
|
+
share(:client, client)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'belafonte'
|
2
|
+
require 'cabal/cli'
|
3
|
+
require 'cabal/cli/authenticated'
|
4
|
+
require 'cabal/ssh'
|
5
|
+
|
6
|
+
module Cabal
|
7
|
+
module CLI
|
8
|
+
class SSH < Belafonte::App
|
9
|
+
include Authenticated
|
10
|
+
|
11
|
+
title "ssh"
|
12
|
+
summary "Connect to a node on a cluster"
|
13
|
+
|
14
|
+
option :cluster_name,
|
15
|
+
short: 'c',
|
16
|
+
long: 'cluster-name',
|
17
|
+
argument: 'CLUSTER_NAME',
|
18
|
+
description: 'The name of the cluster to connect'
|
19
|
+
|
20
|
+
option :lifetime,
|
21
|
+
short: 'l',
|
22
|
+
long: 'lifetime',
|
23
|
+
argument: 'MINUTES',
|
24
|
+
description: 'How long should we forward SSH identities (default: 30, 0 for no timeout)'
|
25
|
+
|
26
|
+
arg :ssh_signature
|
27
|
+
|
28
|
+
def handle
|
29
|
+
with_authentication(partake(:config)) do
|
30
|
+
Cabal::SSH.connect(
|
31
|
+
cluster: option(:cluster_name),
|
32
|
+
ssh_signature: arg(:ssh_signature).first,
|
33
|
+
client: partake(:client),
|
34
|
+
identity_timeout: lifetime,
|
35
|
+
kernel: kernel
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def lifetime
|
41
|
+
return nil unless option(:lifetime)
|
42
|
+
option(:lifetime).to_i * 60
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/cabal/client.rb
CHANGED
@@ -2,11 +2,27 @@ require 'faraday'
|
|
2
2
|
require 'oj'
|
3
3
|
|
4
4
|
module Cabal
|
5
|
+
# A client interface for an upstream Cabal::API
|
5
6
|
class Client
|
7
|
+
# The requested upstream resource was not found
|
8
|
+
NotFoundError = Class.new(StandardError)
|
9
|
+
|
10
|
+
# The user is not authorized to use a given upstream resource
|
11
|
+
UnauthorizedError = Class.new(StandardError)
|
12
|
+
|
13
|
+
# @return [String] the base URL for the v3 API
|
6
14
|
attr_reader :api_base
|
7
15
|
|
16
|
+
# Create a new client instance
|
17
|
+
# @param options [Hash]
|
18
|
+
# @option options [String] :api_base the base URL for the upstream API
|
19
|
+
# (required)
|
20
|
+
# @option options [String] :access_key the access key to use for
|
21
|
+
# authenticated API endpoints (optional)
|
22
|
+
# @option options [String] :secret_key the secret key to use for
|
23
|
+
# authenticated API endpoints (optional)
|
8
24
|
def initialize(options = {})
|
9
|
-
@api_base = "#{options[:api_base]}/
|
25
|
+
@api_base = "#{options[:api_base]}/v3"
|
10
26
|
@access_key = options[:access_key]
|
11
27
|
@secret_key = options[:secret_key]
|
12
28
|
@connector = Faraday.new(url: api_base) do |faraday|
|
@@ -15,15 +31,34 @@ module Cabal
|
|
15
31
|
end
|
16
32
|
end
|
17
33
|
|
34
|
+
# Retrieves a list of cluster names from the upstream API
|
35
|
+
# @return [Array<String>] the names of the known clusters
|
36
|
+
def clusters
|
37
|
+
get("clusters").map {|cluster| cluster['name']}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Retrieves a private SSH key from the upstream API
|
41
|
+
# @param cluster_name [String] the name of the cluster
|
42
|
+
# @return [String] the contents of the cluster's private key
|
18
43
|
def private_key(cluster_name)
|
19
44
|
get("private-key/#{URI.encode(cluster_name)}")['private_ssh_key']
|
20
45
|
end
|
21
46
|
|
47
|
+
# Retrieves a public SSH key from teh upstream API
|
48
|
+
# @param cluster_name [String] the name of the cluster
|
49
|
+
# @return [String] the contents of the cluster's public key
|
22
50
|
def public_key(cluster_name)
|
23
51
|
get("key/#{URI.encode(cluster_name)}")['public_ssh_key']
|
24
52
|
end
|
25
53
|
|
26
|
-
|
54
|
+
# Does an HTTP GET against the upstream API for the provided endpoint
|
55
|
+
# @param endpoint [String] the relative API path to GET
|
56
|
+
# @return [Object] the parsed JSON response
|
57
|
+
# @raise [Cabal::Client::NotFoundError] if the upstream API returns a
|
58
|
+
# 404 status
|
59
|
+
# @raise [Cabal::Client::UnauthorizedError] if the upstream API retirns
|
60
|
+
# a 401 status
|
61
|
+
def get(endpoint)
|
27
62
|
response = connector.get {|req|
|
28
63
|
req.url endpoint
|
29
64
|
req.headers['Content-Type'] = 'application/json'
|
@@ -34,9 +69,9 @@ module Cabal
|
|
34
69
|
when 200
|
35
70
|
Oj.load(response.body)
|
36
71
|
when 404
|
37
|
-
raise
|
72
|
+
raise Cabal::Client::NotFoundError
|
38
73
|
when 401
|
39
|
-
raise
|
74
|
+
raise Cabal::Client::UnauthorizedError
|
40
75
|
end
|
41
76
|
end
|
42
77
|
|
@@ -1,15 +1,32 @@
|
|
1
1
|
module Cabal
|
2
|
+
# Manages ssh-agent sessions for forwarding identity information via
|
3
|
+
# Cabal::SSH
|
2
4
|
class IdentityManager
|
5
|
+
|
6
|
+
# @return [String] the environment settings for the running ssh-agent
|
3
7
|
attr_reader :env
|
4
8
|
|
9
|
+
# Spawns an ssh-agent process and records its related environment settings
|
10
|
+
# @return [nil]
|
5
11
|
def start
|
6
|
-
@env =
|
12
|
+
@env = start_agent.
|
7
13
|
split(/(\;|\n)/).
|
8
|
-
select {|
|
14
|
+
select {|line| line =~ /^SSH_/}.
|
9
15
|
join(' ')
|
10
16
|
end
|
11
17
|
|
18
|
+
# Stops a running ssh-agent that was previously spawned
|
19
|
+
# @return [nil]
|
12
20
|
def stop
|
21
|
+
stop_agent
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def start_agent
|
26
|
+
`ssh-agent`
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop_agent
|
13
30
|
`#{env} ssh-agent -k`
|
14
31
|
end
|
15
32
|
end
|
data/lib/cabal/ssh.rb
CHANGED
@@ -7,37 +7,79 @@ require 'uri'
|
|
7
7
|
require 'fileutils'
|
8
8
|
|
9
9
|
module Cabal
|
10
|
+
# Generates SSH sessions for remote server interaction
|
10
11
|
class SSH
|
11
|
-
|
12
|
-
|
12
|
+
# Create a new instance and use it to generate an SSH session
|
13
|
+
# @param options [Hash] the options to pass along to the new instance
|
14
|
+
# @option options [String] :cluster the name of the cluster key to
|
15
|
+
# use (required)
|
16
|
+
# @option options [String] :ssh_signature the user@hostname
|
17
|
+
# that would be passed if the normal ssh CLI utility were used (required)
|
18
|
+
# @option options [Cabal::Client] :client the instance of
|
19
|
+
# Cabal::Client to use for key retrieval (required)
|
20
|
+
# @option options [Integer] :identity_timeout the number of seconds
|
21
|
+
# that keys should be forwarded in the resulting SSH session (optional)
|
22
|
+
# @option options [Kernel] :kernel the Kernel-compliant object used for
|
23
|
+
# issuing calls to #system for the SSH session (optional)
|
24
|
+
# @return [nil]
|
25
|
+
def self.connect(options = {})
|
26
|
+
new(
|
27
|
+
options[:cluster],
|
28
|
+
options[:ssh_signature],
|
29
|
+
options[:client],
|
30
|
+
options[:identity_timeout] || 1800,
|
31
|
+
options[:kernel] || Kernel
|
32
|
+
).connect
|
13
33
|
end
|
14
34
|
|
15
|
-
def initialize(cluster, ssh_signature, client)
|
35
|
+
def initialize(cluster, ssh_signature, client, identity_timeout, kernel)
|
16
36
|
@cluster = cluster
|
17
37
|
@ssh_signature = ssh_signature
|
18
38
|
@client = client
|
19
|
-
@
|
39
|
+
@identity_timeout = identity_timeout
|
40
|
+
@kernel = kernel
|
41
|
+
end
|
42
|
+
|
43
|
+
# The lifetime option that would be passed to ssh-add for this session
|
44
|
+
# @return [String] the empty string for immortal sessions,
|
45
|
+
# "-t identity_timeout" for sessions with a defined lifetime
|
46
|
+
def lifetime
|
47
|
+
return '' if identity_timeout == 0
|
48
|
+
"-t #{identity_timeout}"
|
20
49
|
end
|
21
50
|
|
51
|
+
# Initiate an SSH connection in the current terminal
|
52
|
+
# @return [nil]
|
22
53
|
def connect
|
54
|
+
begin
|
55
|
+
write_private_key
|
56
|
+
initiate_connection
|
57
|
+
ensure
|
58
|
+
terminate_connection
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def write_private_key
|
23
64
|
private_key = client.private_key(cluster)
|
24
65
|
@key_file = Tempfile.new(SecureRandom.hex(8))
|
25
66
|
key_file.write(private_key)
|
26
67
|
key_file.close
|
68
|
+
FileUtils.chmod(0700, key_file.path)
|
69
|
+
end
|
27
70
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
71
|
+
def initiate_connection
|
72
|
+
agent.start
|
73
|
+
kernel.system("#{agent.env} ssh-add #{lifetime} #{key_file.path} > /dev/null 2>&1")
|
74
|
+
kernel.system("#{agent.env} ssh -A #{ssh_signature}")
|
75
|
+
end
|
76
|
+
|
77
|
+
def terminate_connection
|
78
|
+
kernel.system("#{agent.env} ssh-add -D > /dev/null 2>&1")
|
79
|
+
agent.stop
|
80
|
+
key_file.unlink unless key_file.nil?
|
38
81
|
end
|
39
82
|
|
40
|
-
private
|
41
83
|
def key_file
|
42
84
|
@key_file
|
43
85
|
end
|
@@ -57,5 +99,13 @@ module Cabal
|
|
57
99
|
def agent
|
58
100
|
@agent ||= Cabal::IdentityManager.new
|
59
101
|
end
|
102
|
+
|
103
|
+
def identity_timeout
|
104
|
+
@identity_timeout
|
105
|
+
end
|
106
|
+
|
107
|
+
def kernel
|
108
|
+
@kernel
|
109
|
+
end
|
60
110
|
end
|
61
111
|
end
|
data/lib/cabal/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cabal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dennis Walters
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -123,19 +123,19 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.22'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: toady
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: '0.
|
131
|
+
version: '0.0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: '0.
|
138
|
+
version: '0.0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: cabal-util
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,19 +151,19 @@ dependencies:
|
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0.1'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
|
-
name:
|
154
|
+
name: belafonte
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: '
|
159
|
+
version: '0.5'
|
160
160
|
type: :runtime
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
166
|
+
version: '0.5'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: oj
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -213,9 +213,12 @@ files:
|
|
213
213
|
- exe/cabal
|
214
214
|
- lib/cabal.rb
|
215
215
|
- lib/cabal/cli.rb
|
216
|
+
- lib/cabal/cli/authenticated.rb
|
217
|
+
- lib/cabal/cli/key.rb
|
218
|
+
- lib/cabal/cli/list.rb
|
219
|
+
- lib/cabal/cli/main.rb
|
220
|
+
- lib/cabal/cli/ssh.rb
|
216
221
|
- lib/cabal/client.rb
|
217
|
-
- lib/cabal/commands/key.rb
|
218
|
-
- lib/cabal/commands/ssh.rb
|
219
222
|
- lib/cabal/identity_manager.rb
|
220
223
|
- lib/cabal/ssh.rb
|
221
224
|
- lib/cabal/version.rb
|
data/lib/cabal/commands/key.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module Cabal
|
2
|
-
module Commands
|
3
|
-
module Key
|
4
|
-
def self.bootstrap(obj)
|
5
|
-
obj.instance_eval do
|
6
|
-
desc 'Get the public key for a cluster'
|
7
|
-
long_desc <<-KEYDESC
|
8
|
-
In order to allow logins for Cabal users, you'll need to retrieve
|
9
|
-
a public SSH key for your cluster.
|
10
|
-
KEYDESC
|
11
|
-
|
12
|
-
arg 'cluster name'
|
13
|
-
command :key do |c|
|
14
|
-
c.action do |global_options, options, args|
|
15
|
-
unless global_options[:url]
|
16
|
-
stderr.puts "#{cabal_yml} must contain a :url: definition"
|
17
|
-
exit(1)
|
18
|
-
end
|
19
|
-
|
20
|
-
client = Cabal::Client.new(
|
21
|
-
api_base: global_options[:url],
|
22
|
-
access_key: global_options[:access_key],
|
23
|
-
secret_key: global_options[:secret_key],
|
24
|
-
)
|
25
|
-
stdout.puts client.public_key(args.join(' '))
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/cabal/commands/ssh.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'cabal/ssh'
|
2
|
-
|
3
|
-
module Cabal
|
4
|
-
module Commands
|
5
|
-
module SSH
|
6
|
-
def self.bootstrap(obj)
|
7
|
-
obj.instance_eval do
|
8
|
-
desc 'Connect to a node on a cluster'
|
9
|
-
long_desc <<-SSHDESC
|
10
|
-
SSHDESC
|
11
|
-
|
12
|
-
arg 'ssh signature'
|
13
|
-
command :ssh do |c|
|
14
|
-
c.desc 'The name of the cluster'
|
15
|
-
c.arg_name 'cluster'
|
16
|
-
c.flag [:c, :cluster]
|
17
|
-
c.action do |global_options, options, args|
|
18
|
-
unless global_options[:access_key] && global_options[:secret_key]
|
19
|
-
stderr.puts "You must have an access key and secret key"
|
20
|
-
exit(1)
|
21
|
-
end
|
22
|
-
|
23
|
-
client = Cabal::Client.new(
|
24
|
-
api_base: global_options[:url],
|
25
|
-
access_key: global_options[:access_key],
|
26
|
-
secret_key: global_options[:secret_key]
|
27
|
-
)
|
28
|
-
|
29
|
-
Cabal::SSH.connect(options[:cluster], args.shift, client)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|