rancher-shell 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/rancher-shell.svg)](https://badge.fury.io/rb/rancher-shell)
|
4
|
+
[![Build Status](https://travis-ci.org/marcqualie/rancher-shell.svg?branch=master)](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
|