rancher-shell 0.1.0 → 0.2.1
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/.gitignore +1 -0
- data/CHANGELOG.md +28 -0
- data/README.md +32 -7
- data/bin/rancher-shell +1 -1
- data/lib/rancher/shell/cli.rb +50 -100
- data/lib/rancher/shell/commands/exec.rb +101 -0
- data/lib/rancher/shell/config.rb +53 -0
- data/lib/rancher/shell/version.rb +1 -1
- data/rancher-shell.gemspec +2 -0
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5d382cc4d94f2932837fae38b495d76d837a974
|
4
|
+
data.tar.gz: 1cb18d16e1c84f722e055daad7e339315c3492c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a6987117b98ae7b4a2328b9341e50f7f940e82cab18c6cb5fac1f45715075c89784b44ac57075723cb208e060d867d90b6ac1eab5ea932d48228d5c3c3e51ca
|
7
|
+
data.tar.gz: c71fdc24f744720005a4d31e8c87deb715eb4d8c58658e29d293744d8902b45108d54f13b6b1a9d67f8d2ee8fa5f1be4f81648e5aa8685f50400d66fc7eaabed
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
## [0.2.1] - 2016-03-06
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Commands: list-projects, list-containers
|
13
|
+
- Multi level configuration including root, project and stack level customization
|
14
|
+
- Custom command on boot rather than always using /bin/bash
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
- Input is sent to containers in real-time instead of line by line
|
18
|
+
- Commands such as CTRL+C can now be sent to containers without killing the shell session
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
- BUG: Commands are no longer duplicated back to client due to local buffer caching
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
## [0.1.0] - 2016-02-23
|
26
|
+
|
27
|
+
### Added
|
28
|
+
- Session can now be created entirely from rancher-shell.yml
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# RancherShell
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/rancher-shell)
|
4
|
+
[](https://travis-ci.org/marcqualie/rancher-shell)
|
5
|
+
|
3
6
|
A console utility for shelling into [Rancher](http://rancher.com) containers
|
4
7
|
|
5
8
|
|
@@ -24,27 +27,49 @@ Files are merged using the following schema:
|
|
24
27
|
|
25
28
|
``` yaml
|
26
29
|
---
|
27
|
-
|
30
|
+
# ~/.rancher-shell.yml
|
28
31
|
projects:
|
29
|
-
|
30
|
-
name: "
|
31
|
-
|
32
|
+
project1:
|
33
|
+
name: "My First Project"
|
34
|
+
options:
|
35
|
+
container: production_web_1
|
36
|
+
command: bundle exec rails console
|
37
|
+
stacks:
|
38
|
+
staging:
|
39
|
+
options:
|
40
|
+
container: staging_web_1
|
32
41
|
api:
|
33
42
|
host: rancher.yourdomain.com
|
34
43
|
key: XXXXX
|
35
44
|
secret: XXXXX
|
36
45
|
```
|
37
46
|
|
47
|
+
``` yaml
|
48
|
+
---
|
49
|
+
# /path/to/project1/.rancher-shell.yml
|
50
|
+
options:
|
51
|
+
project: project1
|
52
|
+
projects:
|
53
|
+
project1:
|
54
|
+
stacks:
|
55
|
+
qa:
|
56
|
+
options:
|
57
|
+
container: qa_web_1
|
58
|
+
```
|
59
|
+
|
60
|
+
Running `rancher-shell exec` with the above config will run command `bundle exec rails console` on `project1` within container `production_web_1`. Running `rancher-shell exec -s staging` will run the same command but within container `staging_web_1`. Full usage instructions on how to override these configs is at `rancher-shell help exec`.
|
61
|
+
|
62
|
+
|
38
63
|
|
39
64
|
## Usage
|
40
65
|
|
41
|
-
After configuring you can shell into your container using
|
66
|
+
After configuring you can shell into your container using the following command:
|
42
67
|
|
43
68
|
``` shell
|
44
|
-
rancher-shell
|
69
|
+
rancher-shell exec [-p project] [-s stack] [-c container] [command]
|
45
70
|
```
|
46
71
|
|
47
|
-
Run `rancher-shell
|
72
|
+
Run `rancher-shell help` for full usage instructions
|
48
73
|
|
49
74
|
|
50
75
|
|
data/bin/rancher-shell
CHANGED
data/lib/rancher/shell/cli.rb
CHANGED
@@ -1,118 +1,68 @@
|
|
1
|
-
require 'rancher/shell/
|
2
|
-
require 'rancher/shell/
|
3
|
-
require 'rancher/shell/
|
4
|
-
require '
|
1
|
+
require 'rancher/shell/commands/exec'
|
2
|
+
require 'rancher/shell/config'
|
3
|
+
require 'rancher/shell/version'
|
4
|
+
require 'thor'
|
5
5
|
|
6
6
|
module Rancher
|
7
7
|
module Shell
|
8
|
-
class CLI
|
9
|
-
|
8
|
+
class CLI < Thor
|
9
|
+
map %w[-v --version] => :version
|
10
|
+
desc 'version', 'display gem version'
|
11
|
+
def version
|
12
|
+
puts "Rancher::Shell #{Rancher::Shell::VERSION}"
|
13
|
+
end
|
10
14
|
|
11
|
-
|
12
|
-
|
15
|
+
desc "exec [COMMAND]", "Execute a command within a docker container"
|
16
|
+
option :project, aliases: '-p'
|
17
|
+
option :container, aliases: '-c'
|
18
|
+
option :stack, aliases: '-s'
|
19
|
+
def exec command = nil
|
20
|
+
Config.load(
|
21
|
+
'project' => options[:project],
|
22
|
+
'container' => options[:container],
|
23
|
+
'stack' => options[:stack],
|
24
|
+
'command' => command,
|
25
|
+
)
|
26
|
+
instance = Rancher::Shell::Commands::Exec.new
|
13
27
|
instance.setup_api!
|
14
28
|
instance.retrieve_containers!
|
15
29
|
instance.setup_websocket!
|
16
30
|
instance.listen!
|
17
31
|
end
|
18
32
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
logger.debug "loading config from #{file_path}"
|
30
|
-
config = YAML.load_file(file_path)
|
31
|
-
logger.debug " #{config}"
|
32
|
-
@config.merge! config
|
33
|
-
end
|
34
|
-
end
|
35
|
-
@projects = @config['projects']
|
36
|
-
@project = @config['projects'].find { |project| project['id'] === @config['project'] } || @config['projects'].first
|
37
|
-
logger.info "environment = #{@project['id']} - #{@project['name']}"
|
38
|
-
logger.debug " #{@project}"
|
39
|
-
exit_with_error "API Host Required" unless @project['api'] && @project['api']['host']
|
40
|
-
exit_with_error "API Key Required" unless @project['api'] && @project['api']['key']
|
41
|
-
exit_with_error "API Secret Required" unless @project['api'] && @project['api']['secret']
|
42
|
-
end
|
43
|
-
|
44
|
-
def listen!
|
45
|
-
$stdin.each_line do |command|
|
46
|
-
if command.strip === 'exit'
|
47
|
-
logger.info "connection closed"
|
48
|
-
Kernel.exit true
|
49
|
-
end
|
50
|
-
@websocket.send Base64.encode64 command
|
33
|
+
desc "list-projects", "List all projects available via configuration"
|
34
|
+
def list_projects
|
35
|
+
Config.load
|
36
|
+
projects = Config.get('projects')
|
37
|
+
projects.each do |key, project|
|
38
|
+
print key.ljust 24
|
39
|
+
print project['name'].ljust 32
|
40
|
+
print project['api']['host'].ljust 32
|
41
|
+
print project['stacks'].keys.join(', ') unless project['stacks'].nil?
|
42
|
+
print "\n"
|
51
43
|
end
|
52
44
|
end
|
53
45
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
def retrieve_containers!
|
63
|
-
@response = @api.get(
|
64
|
-
"containers",
|
46
|
+
desc "list-containers", "List all containers available within a project"
|
47
|
+
option :project, aliases: '-p'
|
48
|
+
def list_containers
|
49
|
+
Config.load(
|
50
|
+
'project' => options[:project],
|
65
51
|
)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
'ports' => container['ports'],
|
72
|
-
}
|
73
|
-
end
|
74
|
-
@container = @containers.find { |container| container['name'] === @project['container'] }
|
75
|
-
exit_with_error "could not find container: #{@project['container']}" unless @container
|
76
|
-
end
|
77
|
-
|
78
|
-
def setup_websocket!
|
79
|
-
logger.info "container = #{@container['id']}"
|
80
|
-
@response = @api.post(
|
81
|
-
"containers/#{@container['id']}?action=execute",
|
82
|
-
"command" => [
|
83
|
-
"/bin/sh",
|
84
|
-
"-c",
|
85
|
-
"TERM=xterm-256color; export TERM; [ -x /bin/bash ] && ([ -x /usr/bin/script ] && /usr/bin/script -q -c \"/bin/bash\" /dev/null || exec /bin/bash) || exec /bin/sh",
|
86
|
-
],
|
87
|
-
"attachStdin" => true,
|
88
|
-
"attachStdout" => true,
|
89
|
-
"tty" => false,
|
52
|
+
project = Config.get('project')
|
53
|
+
api = Rancher::Shell::Api.new(
|
54
|
+
host: project['api']['host'],
|
55
|
+
user: project['api']['key'],
|
56
|
+
pass: project['api']['secret'],
|
90
57
|
)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
@buffer ||= ''
|
100
|
-
@buffer << chunk if chunk
|
101
|
-
if chunk.ord === 32
|
102
|
-
emit :message, @buffer
|
103
|
-
@buffer = ''
|
104
|
-
end
|
105
|
-
end
|
106
|
-
@websocket.on :message do |data|
|
107
|
-
$stdout.print data
|
108
|
-
end
|
109
|
-
@websocket.on :error do |event|
|
110
|
-
logger.error "socket error: #{event}"
|
111
|
-
Kernel.exit true
|
112
|
-
end
|
113
|
-
@websocket.on :close do
|
114
|
-
logger.error "closed connection"
|
115
|
-
Kernel.exit true
|
58
|
+
containers = api.get('containers?state=running').json['data']
|
59
|
+
containers.each do |container|
|
60
|
+
print container['id'].ljust 12
|
61
|
+
print container['state'].ljust 12
|
62
|
+
print container['name'].ljust 40
|
63
|
+
print container['imageUuid'][7..-1].ljust 48
|
64
|
+
print container['ports'] if container['ports'] && container['ports'] != ''
|
65
|
+
print "\n"
|
116
66
|
end
|
117
67
|
end
|
118
68
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rancher/shell/api'
|
2
|
+
require 'rancher/shell/logger_helper'
|
3
|
+
require 'rancher/shell/websocket_client'
|
4
|
+
|
5
|
+
module Rancher
|
6
|
+
module Shell
|
7
|
+
module Commands
|
8
|
+
class Exec
|
9
|
+
include LoggerHelper
|
10
|
+
|
11
|
+
attr_reader :api, :websocket
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@config = Config.get_all
|
15
|
+
logger.debug "config = #{@config}"
|
16
|
+
exit_with_error "Project not found: #{@config['project']}" unless @config['project']
|
17
|
+
exit_with_error "Command not specified" unless @config['options']['command'] && @config['options']['command'] != ''
|
18
|
+
exit_with_error "Container not specified" unless @config['options']['container'] && @config['options']['container'] != ''
|
19
|
+
exit_with_error "API Host Required" unless @config['project']['api'] && @config['project']['api']['host']
|
20
|
+
exit_with_error "API Key Required" unless @config['project']['api'] && @config['project']['api']['key']
|
21
|
+
exit_with_error "API Secret Required" unless @config['project']['api'] && @config['project']['api']['secret']
|
22
|
+
end
|
23
|
+
|
24
|
+
def listen!
|
25
|
+
begin
|
26
|
+
logger.info "listening"
|
27
|
+
system("stty raw")
|
28
|
+
while input = STDIN.getc
|
29
|
+
@websocket.send Base64.encode64 input
|
30
|
+
end
|
31
|
+
ensure
|
32
|
+
system("stty -raw echo")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def setup_api!
|
37
|
+
@api = Rancher::Shell::Api.new(
|
38
|
+
host: @config['project']['api']['host'],
|
39
|
+
user: @config['project']['api']['key'],
|
40
|
+
pass: @config['project']['api']['secret'],
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def retrieve_containers!
|
45
|
+
@response = @api.get(
|
46
|
+
"containers",
|
47
|
+
)
|
48
|
+
@containers = @response.json['data'].map do |container|
|
49
|
+
{
|
50
|
+
'id' => container['id'],
|
51
|
+
'name' => container['name'],
|
52
|
+
'state' => container['state'],
|
53
|
+
'ports' => container['ports'],
|
54
|
+
}
|
55
|
+
end
|
56
|
+
@container = @containers.find { |container| container['name'] === @config['options']['container'] }
|
57
|
+
exit_with_error "could not find container: #{@config['options']['container']}" unless @container
|
58
|
+
end
|
59
|
+
|
60
|
+
def setup_websocket!
|
61
|
+
logger.info "container = #{@container['id']}"
|
62
|
+
# default_bash_command = "TERM=xterm-256color; export TERM; [ -x /bin/bash ] && ([ -x /usr/bin/script ] && /usr/bin/script -q -c \"/bin/bash\" /dev/null || exec /bin/bash) || exec /bin/sh"
|
63
|
+
# @config['options']['command'] = default_bash_command if @config['options']['command'] === 'bash'
|
64
|
+
logger.debug "running command: #{@config['options']['command']}"
|
65
|
+
@response = @api.post(
|
66
|
+
"containers/#{@container['id']}?action=execute",
|
67
|
+
"command" => [
|
68
|
+
"/bin/sh",
|
69
|
+
"-c",
|
70
|
+
@config['options']['command'],
|
71
|
+
],
|
72
|
+
"attachStdin" => true,
|
73
|
+
"attachStdout" => true,
|
74
|
+
"tty" => true,
|
75
|
+
)
|
76
|
+
websocket_url = "#{@response.json['url']}?token=#{@response.json['token']}"
|
77
|
+
logger.info "connecting to #{@response.json['url']} ..."
|
78
|
+
@websocket = Rancher::Shell::WebsocketClient.new websocket_url, headers: { 'Authorization' => "Bearer #{@response.json['token']}"}
|
79
|
+
@websocket.on :open do |event|
|
80
|
+
logger.info " connected!"
|
81
|
+
end
|
82
|
+
@websocket.on :chunk do |encoded_chunk|
|
83
|
+
chunk = Base64.decode64 encoded_chunk
|
84
|
+
emit :message, chunk
|
85
|
+
end
|
86
|
+
@websocket.on :message do |data|
|
87
|
+
$stdout.print data
|
88
|
+
end
|
89
|
+
@websocket.on :error do |event|
|
90
|
+
logger.error "socket error: #{event}"
|
91
|
+
Kernel.exit true
|
92
|
+
end
|
93
|
+
@websocket.on :close do
|
94
|
+
logger.error "closed connection"
|
95
|
+
Kernel.exit true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module Rancher
|
5
|
+
module Shell
|
6
|
+
class Config
|
7
|
+
def self.load options = {}
|
8
|
+
config_file_paths = [
|
9
|
+
"#{ENV['HOME']}/.rancher-shell.yml",
|
10
|
+
"#{Dir.pwd}/.rancher-shell.yml",
|
11
|
+
]
|
12
|
+
@data = {}
|
13
|
+
@data['options'] ||= options.dup
|
14
|
+
@data['projects'] ||= {}
|
15
|
+
config_file_paths.each do |file_path|
|
16
|
+
next unless File.exists? file_path
|
17
|
+
config = YAML.load_file file_path
|
18
|
+
if config
|
19
|
+
if config['options']
|
20
|
+
config['options'].each do |key, value|
|
21
|
+
@data['options'][key] = value unless options[key]
|
22
|
+
end
|
23
|
+
config.delete('options')
|
24
|
+
end
|
25
|
+
@data.deep_merge! config
|
26
|
+
end
|
27
|
+
end
|
28
|
+
return unless @data['options']['project']
|
29
|
+
@data['project'] = @data['projects'][@data['options']['project']]
|
30
|
+
if @data['project']['options']
|
31
|
+
@data['project']['options'].each do |key, value|
|
32
|
+
@data['options'][key] = value unless options[key]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if @data['options']['stack'] && @data['project']['stacks'] && @data['project']['stacks'][@data['options']['stack']]
|
36
|
+
if @data['project']['stacks'][@data['options']['stack']]['options']
|
37
|
+
@data['project']['stacks'][@data['options']['stack']]['options'].each do |key, value|
|
38
|
+
@data['options'][key] = value unless options[key]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.get key
|
45
|
+
@data[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.get_all
|
49
|
+
@data
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/rancher-shell.gemspec
CHANGED
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "websocket", "~> 1.2"
|
22
22
|
spec.add_dependency "event_emitter", "~> 0.2.5"
|
23
|
+
spec.add_dependency "thor", "~> 0.19.1"
|
24
|
+
spec.add_dependency "activesupport", "~> 4.2"
|
23
25
|
|
24
26
|
spec.add_development_dependency "bundler", "~> 1.11"
|
25
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rancher-shell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Qualie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.2.5
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.19.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.19.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -91,6 +119,7 @@ files:
|
|
91
119
|
- ".gitignore"
|
92
120
|
- ".rspec"
|
93
121
|
- ".travis.yml"
|
122
|
+
- CHANGELOG.md
|
94
123
|
- CODE_OF_CONDUCT.md
|
95
124
|
- Gemfile
|
96
125
|
- LICENSE.txt
|
@@ -101,6 +130,8 @@ files:
|
|
101
130
|
- lib/rancher/shell/api.rb
|
102
131
|
- lib/rancher/shell/api_response.rb
|
103
132
|
- lib/rancher/shell/cli.rb
|
133
|
+
- lib/rancher/shell/commands/exec.rb
|
134
|
+
- lib/rancher/shell/config.rb
|
104
135
|
- lib/rancher/shell/logger.rb
|
105
136
|
- lib/rancher/shell/logger_helper.rb
|
106
137
|
- lib/rancher/shell/version.rb
|