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 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