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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 66bb59230b610598db1ad66b8254de106c83b108
4
- data.tar.gz: 26954b7fc67e46751fa22b793f54c99382b7d083
3
+ metadata.gz: a5d382cc4d94f2932837fae38b495d76d837a974
4
+ data.tar.gz: 1cb18d16e1c84f722e055daad7e339315c3492c3
5
5
  SHA512:
6
- metadata.gz: d2e5d5f73dc438e3b6f1a8181189209d0711943c618d263ad3708006729171e5ba2996b6ea9936ea751573a68d96991ab56884c28bf08c6247badfda561a1f19
7
- data.tar.gz: 0141f88a60a95ad47c95cbb9123ec1dcef5d54fe98d720a07c4aac46abc5cf2c3295d2ef97c1d2c544e0b04a22f96cf1762759e96df78fea1b7037e9d15cc503
6
+ metadata.gz: 1a6987117b98ae7b4a2328b9341e50f7f940e82cab18c6cb5fac1f45715075c89784b44ac57075723cb208e060d867d90b6ac1eab5ea932d48228d5c3c3e51ca
7
+ data.tar.gz: c71fdc24f744720005a4d31e8c87deb715eb4d8c58658e29d293744d8902b45108d54f13b6b1a9d67f8d2ee8fa5f1be4f81648e5aa8685f50400d66fc7eaabed
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .env
3
3
  Gemfile.lock
4
4
  *.gem
5
+ .rancher-shell.yml
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
- project: 1a234
30
+ # ~/.rancher-shell.yml
28
31
  projects:
29
- - id: 1a234
30
- name: "Production"
31
- container: rails_web_1
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 a single command:
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 --help` for full usage instructions
72
+ Run `rancher-shell help` for full usage instructions
48
73
 
49
74
 
50
75
 
data/bin/rancher-shell CHANGED
@@ -2,4 +2,4 @@
2
2
  $:.unshift 'lib'
3
3
  require 'rancher/shell/cli'
4
4
 
5
- Rancher::Shell::CLI.start
5
+ Rancher::Shell::CLI.start ARGV
@@ -1,118 +1,68 @@
1
- require 'rancher/shell/api'
2
- require 'rancher/shell/logger_helper'
3
- require 'rancher/shell/websocket_client'
4
- require 'yaml'
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
- include LoggerHelper
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
- def self.start
12
- instance = self.new
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
- attr_reader :api, :websocket
20
-
21
- def initialize
22
- @config_file_paths = [
23
- "#{ENV['HOME']}/.rancher-shell.yml",
24
- "#{Dir.pwd}/.rancher-shell.yml",
25
- ]
26
- @config = {}
27
- @config_file_paths.each do |file_path|
28
- if File.exists? file_path
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
- def setup_api!
55
- @api = Rancher::Shell::Api.new(
56
- host: @project['api']['host'],
57
- user: @project['api']['key'],
58
- pass: @project['api']['secret'],
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
- @containers = @response.json['data'].map do |container|
67
- {
68
- 'id' => container['id'],
69
- 'name' => container['name'],
70
- 'state' => container['state'],
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
- websocket_url = "#{@response.json['url']}?token=#{@response.json['token']}"
92
- logger.info "connecting to #{@response.json['url']} ..."
93
- @websocket = Rancher::Shell::WebsocketClient.new websocket_url, headers: { 'Authorization' => "Bearer #{@response.json['token']}"}
94
- @websocket.on :open do |event|
95
- logger.info " connected!"
96
- end
97
- @websocket.on :chunk do |encoded_chunk|
98
- chunk = Base64.decode64 encoded_chunk
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
@@ -1,5 +1,5 @@
1
1
  module Rancher
2
2
  module Shell
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -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.0
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-02-23 00:00:00.000000000 Z
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