docker-pier 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +4 -0
- data/README.md +46 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-pier.gemspec +36 -0
- data/exe/docker-pier +197 -0
- data/lib/docker-pier.rb +4 -0
- data/lib/docker-pier/log-stream.rb +94 -0
- data/lib/docker-pier/log-stream/palette.rb +164 -0
- data/lib/docker-pier/node.rb +20 -0
- data/lib/docker-pier/pier.rb +102 -0
- data/lib/docker-pier/version.rb +3 -0
- data/lib/docker/swarm.rb +18 -0
- data/lib/docker/swarm/node.rb +26 -0
- data/lib/docker/swarm/service.rb +17 -0
- data/lib/docker/swarm/task.rb +33 -0
- metadata +274 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4db1df031b3184db08b38589afed042f43c93f5c
|
4
|
+
data.tar.gz: 56f5f252bebdc7300fdf8d109b69827d08125dc3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 621f0d87acabf5d89435c3b5597edee4cdc73a1a61838f1250f8fb2da630941538de95f68a1a0470fe9e400906baa9c61209ba7947883a73e1b48f3a004122fb
|
7
|
+
data.tar.gz: eea7967dbb62ab38d06d9047981a6672ad91849770d70967e3504d425d3277e169cd1b70603784dbaf959ee5a48150b1adf0a6b39c0c2f9b2723d155cee8b9f0
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Pier
|
2
|
+
|
3
|
+
Pier is an alternative Docker client, which uses the Ruby [docker-api](https://github.com/swipely/docker-api) gem to access the Docker Remote API. Pier is focused on the use-case of managing one or more clusters of machines which are entirely dedicated to running a Docker swarm.
|
4
|
+
|
5
|
+
Pier uses [fog](http://fog.io/) for its instance management. For now, only the [fog-libvirt](https://github.com/fog/fog-libvirt) backend is supported.
|
6
|
+
|
7
|
+
Pier assumes that each cluster has a cluster-manager endpoint, called a "pier." The pier is a running instance that must:
|
8
|
+
|
9
|
+
* be accessible via SSH;
|
10
|
+
* be a manager in your Docker swarm; and
|
11
|
+
* have `easy-rsa` installed and set up to generate both server and client certificates.
|
12
|
+
|
13
|
+
A pier need not be a member of the cluster itself. Pier does not manage the pier, only the cluster visible through the pier.
|
14
|
+
|
15
|
+
By default, Pier also assumes that each pier is the _libvirt hypervisor_ for its cluster—i.e. that libvirt's endpoint is accessible as `qemu+ssh://[your-pier-endpoint]/system`.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'docker-pier'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install docker-pier
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
TODO: Write usage instructions here
|
36
|
+
|
37
|
+
## Development
|
38
|
+
|
39
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
40
|
+
|
41
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tsutsu/docker-pier.
|
46
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "docker-pier"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
ENV['PIER'] ||= '//tsutsu@vizier.staging.meetwalter.co'
|
10
|
+
|
11
|
+
include DockerPier
|
12
|
+
|
13
|
+
require "pry"
|
14
|
+
Pry.start
|
data/bin/setup
ADDED
data/docker-pier.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'docker-pier/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "docker-pier"
|
8
|
+
spec.version = DockerPier::VERSION
|
9
|
+
spec.authors = ["Levi Aul"]
|
10
|
+
spec.email = ["levi@leviaul.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An alternative Docker client for dedicated Docker Swarm clusters}
|
13
|
+
spec.homepage = "https://github.com/tsutsu/docker-pier"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
|
25
|
+
spec.add_runtime_dependency "fog-libvirt", "~> 0.3.0"
|
26
|
+
spec.add_runtime_dependency "docker-api", "~> 1.32", ">= 1.32.1"
|
27
|
+
spec.add_runtime_dependency "net-ssh", "~> 3.2"
|
28
|
+
spec.add_runtime_dependency "net-scp", "~> 1.2", ">= 1.2.1"
|
29
|
+
spec.add_runtime_dependency "highline", "~> 1.7", ">= 1.7.8"
|
30
|
+
spec.add_runtime_dependency "pry", "~> 0.10.4"
|
31
|
+
spec.add_runtime_dependency "main", "~> 6.2", ">= 6.2.1"
|
32
|
+
spec.add_runtime_dependency "sequel", "~> 4.39"
|
33
|
+
spec.add_runtime_dependency "amalgalite", "~> 1.5"
|
34
|
+
spec.add_runtime_dependency "color", "~> 1.8"
|
35
|
+
spec.add_runtime_dependency "paint", "~> 1.0", ">= 1.0.1"
|
36
|
+
end
|
data/exe/docker-pier
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "docker-pier"
|
3
|
+
require "main"
|
4
|
+
require "highline"
|
5
|
+
|
6
|
+
Main do
|
7
|
+
db do
|
8
|
+
create_table(:clusters) do
|
9
|
+
primary_key :id
|
10
|
+
String :name, unique: true, null: false
|
11
|
+
String :pier_uri, null: false
|
12
|
+
end unless table_exists?(:clusters)
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
$stderr.puts "use `#{File.basename($0)} --help` for usage"
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
mode 'list' do
|
21
|
+
def run
|
22
|
+
db[:clusters].each do |cluster|
|
23
|
+
puts "#{cluster[:name]}: #{cluster[:pier_uri]}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
mode 'detach' do
|
29
|
+
argument('name'){
|
30
|
+
description "a cluster nickname"
|
31
|
+
required
|
32
|
+
}
|
33
|
+
|
34
|
+
def run
|
35
|
+
cluster_name = params[:name].values.first
|
36
|
+
pier = db[:clusters].where(name: cluster_name).delete
|
37
|
+
|
38
|
+
in_use_cluster_name = ENV['DOCKER_MACHINE_NAME']
|
39
|
+
if cluster_name == in_use_cluster_name
|
40
|
+
puts <<~EOF
|
41
|
+
unset DOCKER_TLS_VERIFY
|
42
|
+
unset DOCKER_HOST
|
43
|
+
unset DOCKER_CERT_PATH
|
44
|
+
unset DOCKER_MACHINE_NAME
|
45
|
+
EOF
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
mode 'attach' do
|
51
|
+
argument('name'){
|
52
|
+
description "nickname for the new cluster"
|
53
|
+
required
|
54
|
+
}
|
55
|
+
|
56
|
+
argument('uri'){
|
57
|
+
description "URI of the cluster's pier"
|
58
|
+
required
|
59
|
+
}
|
60
|
+
|
61
|
+
def run
|
62
|
+
cluster_name = params[:name].values.first
|
63
|
+
pier_uri = URI.parse('//' + params[:uri].values.first + '/')
|
64
|
+
|
65
|
+
# test validity
|
66
|
+
pier = DockerPier::Pier.new(pier_uri, name: cluster_name)
|
67
|
+
pier.bootstrap!
|
68
|
+
|
69
|
+
pier.libvirt.get_node_info
|
70
|
+
Docker.info(pier.docker)
|
71
|
+
pier.ssh.exec! 'pwd'
|
72
|
+
|
73
|
+
db[:clusters].insert(nil, cluster_name, pier_uri.to_s)
|
74
|
+
|
75
|
+
puts <<~EOF
|
76
|
+
export DOCKER_TLS_VERIFY=1
|
77
|
+
export DOCKER_HOST=#{pier.resolved_docker_uri}
|
78
|
+
export DOCKER_CERT_PATH=#{pier.x509_dir}
|
79
|
+
export DOCKER_MACHINE_NAME=#{cluster_name}
|
80
|
+
EOF
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
mode 'on' do
|
85
|
+
argument('name'){
|
86
|
+
description "nickname of the cluster to switch to"
|
87
|
+
required
|
88
|
+
}
|
89
|
+
|
90
|
+
def run
|
91
|
+
cluster_name = params[:name].values.first
|
92
|
+
pier = db[:clusters].where(name: cluster_name).first
|
93
|
+
unless pier
|
94
|
+
$stderr.puts "unknown cluster '#{cluster_name}'"
|
95
|
+
return false
|
96
|
+
end
|
97
|
+
|
98
|
+
pier = DockerPier::Pier.new(pier[:pier_uri], name: pier[:name])
|
99
|
+
|
100
|
+
puts <<~EOF
|
101
|
+
export DOCKER_TLS_VERIFY=1
|
102
|
+
export DOCKER_HOST=#{pier.resolved_docker_uri}
|
103
|
+
export DOCKER_CERT_PATH=#{pier.x509_dir}
|
104
|
+
export DOCKER_MACHINE_NAME=#{cluster_name}
|
105
|
+
EOF
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
mode 'off' do
|
110
|
+
def run
|
111
|
+
puts <<~EOF
|
112
|
+
unset DOCKER_TLS_VERIFY
|
113
|
+
unset DOCKER_HOST
|
114
|
+
unset DOCKER_CERT_PATH
|
115
|
+
unset DOCKER_MACHINE_NAME
|
116
|
+
EOF
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
mode 'hook:install' do
|
122
|
+
def run
|
123
|
+
puts <<~EOF
|
124
|
+
pier ()
|
125
|
+
{
|
126
|
+
local subcommand="$1"
|
127
|
+
case "${subcommand}" in
|
128
|
+
on|off|attach|detach)
|
129
|
+
shift
|
130
|
+
eval "$(command docker-pier "${subcommand}" "$@")"
|
131
|
+
;;
|
132
|
+
*)
|
133
|
+
command docker-pier "$@"
|
134
|
+
;;
|
135
|
+
esac
|
136
|
+
}
|
137
|
+
EOF
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
mode 'nodes' do
|
143
|
+
def run
|
144
|
+
cluster_name = ENV['DOCKER_MACHINE_NAME']
|
145
|
+
pier = db[:clusters].where(name: cluster_name).first
|
146
|
+
unless pier
|
147
|
+
$stderr.puts "unknown cluster '#{cluster_name}'"
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
pier = DockerPier::Pier.new(pier[:pier_uri], name: pier[:name])
|
151
|
+
|
152
|
+
pier.nodes.each do |node|
|
153
|
+
puts node.inspect
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
mode 'logs' do
|
159
|
+
def run
|
160
|
+
cluster_name = ENV['DOCKER_MACHINE_NAME']
|
161
|
+
pier = db[:clusters].where(name: cluster_name).first
|
162
|
+
unless pier
|
163
|
+
$stderr.puts "unknown cluster '#{cluster_name}'"
|
164
|
+
return false
|
165
|
+
end
|
166
|
+
pier = DockerPier::Pier.new(pier[:pier_uri], name: pier[:name])
|
167
|
+
|
168
|
+
handle_maxlen = 0
|
169
|
+
tty_width = HighLine::SystemExtensions.terminal_size[0]
|
170
|
+
|
171
|
+
pier.logs.stream! do |event|
|
172
|
+
next if event.lines.empty?
|
173
|
+
|
174
|
+
handle_ansi = event.task.name
|
175
|
+
handle_maxlen = [handle_maxlen, handle_ansi.length].max
|
176
|
+
handle_ansi = handle_ansi.rjust(handle_maxlen, ' ')
|
177
|
+
handle_ansi = Paint[handle_ansi, event.task.color]
|
178
|
+
|
179
|
+
time_ansi = Paint[event.time.strftime("%H:%M:%S"), :white]
|
180
|
+
|
181
|
+
ln1_prefix_ansi = "%s %s " % [handle_ansi, time_ansi]
|
182
|
+
lns_indent = ' ' * Paint.unpaint(ln1_prefix_ansi).length
|
183
|
+
lns_color = (event.type == :error) ? :red : :default
|
184
|
+
|
185
|
+
tty_remain = tty_width - lns_indent.length
|
186
|
+
lns = event.lines.dup
|
187
|
+
lns = lns.map{ |ln| ln.gsub(/(.{1,#{tty_remain}})/, "\\1\n").split("\n") }.flatten
|
188
|
+
|
189
|
+
puts ln1_prefix_ansi + Paint[lns.shift, lns_color]
|
190
|
+
|
191
|
+
lns.each do |ln|
|
192
|
+
puts lns_indent + Paint[ln, lns_color]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
data/lib/docker-pier.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'json'
|
4
|
+
require 'paint'
|
5
|
+
require 'date'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
require 'docker-pier/log-stream/palette'
|
9
|
+
|
10
|
+
class Pathname
|
11
|
+
def to_shell
|
12
|
+
Shellwords.escape(self.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class DockerPier::LogStream
|
17
|
+
def initialize(pier)
|
18
|
+
@pier = pier
|
19
|
+
@palette = DockerPier::LogStream::Palette.new
|
20
|
+
@task_colors = Hash.new{ |h,k| h[k] = @palette.draw! }
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@path ||= get_path!
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_path!
|
28
|
+
logs_dir = Pathname.new '/var/log/fluentd'
|
29
|
+
logfile_link = logs_dir + 'docker.log'
|
30
|
+
|
31
|
+
# fluentd creates the symlink with an absolute path from inside a container,
|
32
|
+
# so the dir the link points to is wrong. We assume the logs dir instead.
|
33
|
+
logs_link_dest = Pathname.new(@pier.ssh.exec!("readlink #{logfile_link.to_shell}").chomp)
|
34
|
+
logs_dir + logs_link_dest.basename
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_log_ln(ln)
|
38
|
+
ts, fluent_source_id, event_json = ln.split(/\s+/, 3)
|
39
|
+
|
40
|
+
ts = DateTime.parse(ts)
|
41
|
+
event = JSON.parse(event_json)
|
42
|
+
event_type = (event.delete('source') == 'stderr') ? :error : :output
|
43
|
+
|
44
|
+
container_id = event.delete('container_name')[1..-1]
|
45
|
+
msg = event.delete('log')
|
46
|
+
|
47
|
+
if msg[0] == '{'
|
48
|
+
msg_parts = JSON.parse(msg)
|
49
|
+
msg_lns = msg_parts.delete('lines').map{ |ln| ln.chomp }
|
50
|
+
msg_parts = OpenStruct.new(msg_parts)
|
51
|
+
else
|
52
|
+
msg_lns = msg.split("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
service_parts = container_id.match(/^(\w+)\.(\d+)\.(\w+)$/)
|
56
|
+
service = service_parts ? [service_parts[1], service_parts[2].to_i] : nil
|
57
|
+
|
58
|
+
task_handle = service ? ("%s[%02d]" % service) : container_id
|
59
|
+
task_color = @task_colors[task_handle]
|
60
|
+
|
61
|
+
OpenStruct.new(
|
62
|
+
time: ts,
|
63
|
+
type: event_type,
|
64
|
+
service: service,
|
65
|
+
task: OpenStruct.new(name: task_handle, color: task_color),
|
66
|
+
location: [:some_node, container_id],
|
67
|
+
message: msg_parts,
|
68
|
+
lines: msg_lns
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def stream!
|
73
|
+
ssh_session = @pier.ssh
|
74
|
+
|
75
|
+
channel = ssh_session.open_channel do |ch|
|
76
|
+
ch.exec "tail -f #{self.path.to_shell}" do |ch, success|
|
77
|
+
raise "could not tail logs on remote" unless success
|
78
|
+
|
79
|
+
ch.on_data do |_, data|
|
80
|
+
data.split("\n").each do |ln|
|
81
|
+
event = parse_log_ln(ln)
|
82
|
+
yield(event)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
int_pressed = false
|
89
|
+
trap("INT") { int_pressed = true }
|
90
|
+
ssh_session.loop(0.1) { not int_pressed }
|
91
|
+
|
92
|
+
ssh_session.shutdown!
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
class DockerPier::LogStream; end
|
2
|
+
|
3
|
+
class DockerPier::LogStream::Palette
|
4
|
+
def initialize
|
5
|
+
@bag = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def draw!
|
9
|
+
if @bag.empty?
|
10
|
+
i = rand(BASE_COLORS.length)
|
11
|
+
@bag = BASE_COLORS[i..-1] + BASE_COLORS[0...i]
|
12
|
+
end
|
13
|
+
|
14
|
+
@bag.shift
|
15
|
+
end
|
16
|
+
|
17
|
+
BASE_COLORS = [
|
18
|
+
[0, 238, 0],
|
19
|
+
[148, 0, 211],
|
20
|
+
[255, 0, 255],
|
21
|
+
[255, 0, 0],
|
22
|
+
[104, 34, 139],
|
23
|
+
[139, 26, 26],
|
24
|
+
[127, 255, 0],
|
25
|
+
[0, 191, 255],
|
26
|
+
[255, 0, 255],
|
27
|
+
[0, 205, 0],
|
28
|
+
[139, 28, 98],
|
29
|
+
[127, 255, 0],
|
30
|
+
[255, 0, 0],
|
31
|
+
[0, 191, 255],
|
32
|
+
[0, 255, 0],
|
33
|
+
[199, 21, 133],
|
34
|
+
[238, 0, 238],
|
35
|
+
[0, 255, 0],
|
36
|
+
[205, 0, 0],
|
37
|
+
[124, 252, 0],
|
38
|
+
[199, 21, 133],
|
39
|
+
[124, 252, 0],
|
40
|
+
[205, 16, 118],
|
41
|
+
[0, 191, 255],
|
42
|
+
[102, 205, 0],
|
43
|
+
[208, 32, 144],
|
44
|
+
[238, 0, 0],
|
45
|
+
[118, 238, 0],
|
46
|
+
[0, 0, 205],
|
47
|
+
[208, 32, 144],
|
48
|
+
[0, 0, 205],
|
49
|
+
[139, 34, 82],
|
50
|
+
[255, 165, 0],
|
51
|
+
[0, 178, 238],
|
52
|
+
[0, 0, 205],
|
53
|
+
[154, 205, 50],
|
54
|
+
[205, 0, 205],
|
55
|
+
[154, 205, 50],
|
56
|
+
[255, 69, 0],
|
57
|
+
[148, 0, 211],
|
58
|
+
[154, 205, 50],
|
59
|
+
[215, 7, 81],
|
60
|
+
[255, 165, 0],
|
61
|
+
[153, 50, 204],
|
62
|
+
[105, 139, 34],
|
63
|
+
[255, 69, 0],
|
64
|
+
[153, 50, 204],
|
65
|
+
[107, 142, 35],
|
66
|
+
[154, 50, 205],
|
67
|
+
[107, 142, 35],
|
68
|
+
[255, 69, 0],
|
69
|
+
[205, 41, 144],
|
70
|
+
[85, 107, 47],
|
71
|
+
[122, 55, 139],
|
72
|
+
[238, 173, 14],
|
73
|
+
[34, 139, 34],
|
74
|
+
[139, 35, 35],
|
75
|
+
[34, 139, 34],
|
76
|
+
[0, 0, 238],
|
77
|
+
[255, 140, 0],
|
78
|
+
[0, 255, 255],
|
79
|
+
[255, 140, 0],
|
80
|
+
[85, 107, 47],
|
81
|
+
[0, 255, 255],
|
82
|
+
[255, 127, 0],
|
83
|
+
[0, 245, 255],
|
84
|
+
[85, 26, 139],
|
85
|
+
[178, 34, 34],
|
86
|
+
[0, 0, 255],
|
87
|
+
[238, 64, 0],
|
88
|
+
[205, 50, 120],
|
89
|
+
[50, 205, 50],
|
90
|
+
[0, 0, 255],
|
91
|
+
[238, 154, 0],
|
92
|
+
[50, 205, 50],
|
93
|
+
[205, 38, 38],
|
94
|
+
[0, 154, 205],
|
95
|
+
[205, 55, 0],
|
96
|
+
[16, 78, 139],
|
97
|
+
[165, 42, 42],
|
98
|
+
[176, 48, 96],
|
99
|
+
[0, 255, 127],
|
100
|
+
[238, 118, 0],
|
101
|
+
[0, 238, 238],
|
102
|
+
[0, 255, 127],
|
103
|
+
[39, 64, 139],
|
104
|
+
[0, 255, 127],
|
105
|
+
[218, 165, 32],
|
106
|
+
[0, 229, 238],
|
107
|
+
[110, 139, 61],
|
108
|
+
[0, 250, 154],
|
109
|
+
[139, 58, 98],
|
110
|
+
[0, 250, 154],
|
111
|
+
[139, 54, 38],
|
112
|
+
[205, 149, 12],
|
113
|
+
[139, 71, 137],
|
114
|
+
[205, 155, 29],
|
115
|
+
[24, 116, 205],
|
116
|
+
[139, 69, 19],
|
117
|
+
[139, 69, 19],
|
118
|
+
[0, 238, 118],
|
119
|
+
[205, 133, 0],
|
120
|
+
[139, 69, 19],
|
121
|
+
[205, 102, 0],
|
122
|
+
[139, 58, 58],
|
123
|
+
[71, 60, 139],
|
124
|
+
[184, 134, 11],
|
125
|
+
[184, 134, 11],
|
126
|
+
[72, 61, 139],
|
127
|
+
[72, 61, 139],
|
128
|
+
[210, 105, 30],
|
129
|
+
[139, 62, 47],
|
130
|
+
[0, 206, 209],
|
131
|
+
[0, 206, 209],
|
132
|
+
[205, 102, 29],
|
133
|
+
[139, 105, 20],
|
134
|
+
[125, 38, 205],
|
135
|
+
[0, 205, 102],
|
136
|
+
[0, 205, 205],
|
137
|
+
[0, 197, 205],
|
138
|
+
[139, 71, 93],
|
139
|
+
[139, 71, 38],
|
140
|
+
[84, 139, 84],
|
141
|
+
[70, 130, 180],
|
142
|
+
[70, 130, 180],
|
143
|
+
[54, 100, 139],
|
144
|
+
[160, 82, 45],
|
145
|
+
[46, 139, 87],
|
146
|
+
[46, 139, 87],
|
147
|
+
[46, 139, 87],
|
148
|
+
[60, 179, 113],
|
149
|
+
[60, 179, 113],
|
150
|
+
[139, 76, 57],
|
151
|
+
[139, 90, 43],
|
152
|
+
[93, 71, 139],
|
153
|
+
[32, 178, 170],
|
154
|
+
[32, 178, 170],
|
155
|
+
[139, 87, 66],
|
156
|
+
[74, 112, 139],
|
157
|
+
[69, 139, 116],
|
158
|
+
[139, 115, 85],
|
159
|
+
[95, 158, 160],
|
160
|
+
[95, 158, 160],
|
161
|
+
[82, 139, 139],
|
162
|
+
[83, 134, 139]
|
163
|
+
]
|
164
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'docker-pier/version'
|
2
|
+
require 'fog/libvirt'
|
3
|
+
require 'docker/swarm'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class DockerPier::Node < SimpleDelegator
|
7
|
+
def initialize(docker_node, pier)
|
8
|
+
@docker_node = docker_node
|
9
|
+
@pier = pier
|
10
|
+
super @docker_node
|
11
|
+
end
|
12
|
+
|
13
|
+
def libvirt_node
|
14
|
+
@libvirt_node ||= (@pier.libvirt.servers.all(name: @docker_node.info['Description']['Hostname']).first rescue nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#<DockerPier::Node %s/%s>" % [@pier.name, @docker_node.info['Description']['Hostname']]
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'docker-pier/version'
|
2
|
+
require 'docker'
|
3
|
+
require 'docker/swarm'
|
4
|
+
require 'fog/libvirt'
|
5
|
+
require 'net/ssh'
|
6
|
+
require 'net/scp'
|
7
|
+
require 'resolv'
|
8
|
+
require 'pathname'
|
9
|
+
|
10
|
+
class DockerPier::Pier
|
11
|
+
def self.current
|
12
|
+
@current ||= self.new(ENV['PIER'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(pier_uri, opts = {})
|
16
|
+
@name = opts.delete(:name) || ENV['DOCKER_MACHINE_NAME'] || Digest::SHA1.hexdigest(pier_uri)
|
17
|
+
|
18
|
+
pier_uri = URI.parse(pier_uri.to_s) unless pier_uri.kind_of?(URI)
|
19
|
+
|
20
|
+
@libvirt_uri = pier_uri.dup.tap do |uri|
|
21
|
+
uri.scheme = 'qemu+ssh'
|
22
|
+
uri.path = '/system'
|
23
|
+
uri.query = 'socket=/var/run/libvirt/libvirt-sock'
|
24
|
+
end
|
25
|
+
|
26
|
+
@docker_uri = pier_uri.dup.tap do |uri|
|
27
|
+
uri.scheme = 'tcp'
|
28
|
+
uri.user = nil
|
29
|
+
uri.port = 2376
|
30
|
+
uri.path = '/'
|
31
|
+
end
|
32
|
+
|
33
|
+
@ssh_uri = pier_uri.dup.tap do |uri|
|
34
|
+
uri.scheme = 'ssh'
|
35
|
+
uri.port = 22
|
36
|
+
uri.path = '/'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :name
|
41
|
+
attr_reader :libvirt_uri
|
42
|
+
attr_reader :docker_uri
|
43
|
+
attr_reader :ssh_uri
|
44
|
+
|
45
|
+
|
46
|
+
def x509_dir
|
47
|
+
Pathname.new(ENV['HOME']) + '.docker-pier' + 'x509' + @name
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolved_docker_uri
|
51
|
+
@docker_uri.dup.tap{ |u| u.host = Resolv.getaddress(u.host) }
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def libvirt
|
56
|
+
@libvirt ||= Fog::Compute.new(provider: "Libvirt", libvirt_uri: @libvirt_uri.to_s)
|
57
|
+
end
|
58
|
+
|
59
|
+
def docker
|
60
|
+
@docker ||= Docker::Connection.new(self.resolved_docker_uri.to_s,
|
61
|
+
scheme: "https",
|
62
|
+
ssl_ca_file: (self.x509_dir + "ca.pem").to_s,
|
63
|
+
client_cert: (self.x509_dir + "cert.pem").to_s,
|
64
|
+
client_key: (self.x509_dir + "key.pem").to_s
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def ssh
|
69
|
+
@ssh ||= Net::SSH.start(@ssh_uri.hostname, @ssh_uri.user, password: @ssh_uri.password)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def bootstrap!
|
74
|
+
own_hostname = `hostname -f`.chomp
|
75
|
+
remote_export_path = Pathname.new('/var/lib/private-x509/clients') + "#{own_hostname}.tar"
|
76
|
+
|
77
|
+
self.ssh.exec!(<<~EOF)
|
78
|
+
if [ ! -f '#{remote_export_path}' ]; then
|
79
|
+
/var/lib/private-x509/gen_bundle '#{own_hostname}'
|
80
|
+
fi
|
81
|
+
EOF
|
82
|
+
|
83
|
+
local_export_path = self.x509_dir + 'export.tar'
|
84
|
+
|
85
|
+
self.x509_dir.mkpath
|
86
|
+
Dir.chdir(self.x509_dir.to_s) do
|
87
|
+
self.ssh.scp.download! remote_export_path.to_s, local_export_path.to_s
|
88
|
+
system 'tar', '-x', '-f', local_export_path.to_s
|
89
|
+
local_export_path.unlink
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def nodes
|
95
|
+
docker_nodes = Docker::Swarm::Node.all({}, self.docker)
|
96
|
+
docker_nodes.map{ |d| DockerPier::Node.new(d, self) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def logs
|
100
|
+
DockerPier::LogStream.new(self)
|
101
|
+
end
|
102
|
+
end
|
data/lib/docker/swarm.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class Docker::Swarm; end
|
2
|
+
|
3
|
+
require 'docker/swarm/node'
|
4
|
+
require 'docker/swarm/service'
|
5
|
+
require 'docker/swarm/task'
|
6
|
+
|
7
|
+
# This class represents a Docker Swarm.
|
8
|
+
class Docker::Swarm
|
9
|
+
include Docker::Base
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def get(opts = {}, conn = Docker.connection)
|
13
|
+
swarm_json = conn.get("/swarm", opts)
|
14
|
+
hash = Docker::Util.parse_json(swarm_json) || {}
|
15
|
+
new(conn, hash)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
# This class represents a Docker Swarm Node.
|
4
|
+
class Docker::Swarm::Node
|
5
|
+
include Docker::Base
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def get(id, opts = {}, conn = Docker.connection)
|
9
|
+
node_json = conn.get("/nodes/#{URI.encode(id)}", opts)
|
10
|
+
hash = Docker::Util.parse_json(node_json) || {}
|
11
|
+
new(conn, hash)
|
12
|
+
end
|
13
|
+
|
14
|
+
def by_name(node_name, opts = {}, conn = Docker.connection)
|
15
|
+
opts = opts.merge(filters: {"name" => [node_name]}.to_json)
|
16
|
+
hashes = Docker::Util.parse_json(conn.get('/nodes', opts)) || []
|
17
|
+
hash = hashes.first
|
18
|
+
new(conn, hash) if hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def all(opts = {}, conn = Docker.connection)
|
22
|
+
hashes = Docker::Util.parse_json(conn.get('/nodes', opts)) || []
|
23
|
+
hashes.map { |hash| new(conn, hash) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# This class represents a Docker Swarm Service.
|
2
|
+
class Docker::Swarm::Service
|
3
|
+
include Docker::Base
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def get(id, opts = {}, conn = Docker.connection)
|
7
|
+
service_json = conn.get("/services/#{URI.encode(id)}", opts)
|
8
|
+
hash = Docker::Util.parse_json(service_json) || {}
|
9
|
+
new(conn, hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def all(opts = {}, conn = Docker.connection)
|
13
|
+
hashes = Docker::Util.parse_json(conn.get('/services', opts)) || []
|
14
|
+
hashes.map { |hash| new(conn, hash) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# This class represents a Docker Swarm Task.
|
2
|
+
class Docker::Swarm::Task
|
3
|
+
include Docker::Base
|
4
|
+
|
5
|
+
def service
|
6
|
+
Docker::Service.get(self.info["ServiceID"])
|
7
|
+
end
|
8
|
+
|
9
|
+
def node
|
10
|
+
Docker::Node.get(self.info["NodeID"])
|
11
|
+
end
|
12
|
+
|
13
|
+
def state
|
14
|
+
self.info['Status']['State'].intern
|
15
|
+
end
|
16
|
+
|
17
|
+
def running?
|
18
|
+
self.state == :running
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def get(id, opts = {}, conn = Docker.connection)
|
23
|
+
task_json = conn.get("/tasks/#{URI.encode(id)}", opts)
|
24
|
+
hash = Docker::Util.parse_json(task_json) || {}
|
25
|
+
new(conn, hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
def all(opts = {}, conn = Docker.connection)
|
29
|
+
hashes = Docker::Util.parse_json(conn.get('/tasks', opts)) || []
|
30
|
+
hashes.map { |hash| new(conn, hash) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-pier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Levi Aul
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: fog-libvirt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: docker-api
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.32'
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 1.32.1
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '1.32'
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 1.32.1
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: net-ssh
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.2'
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3.2'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: net-scp
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.2'
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 1.2.1
|
99
|
+
type: :runtime
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1.2'
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 1.2.1
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: highline
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - "~>"
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '1.7'
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: 1.7.8
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '1.7'
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: 1.7.8
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: pry
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 0.10.4
|
136
|
+
type: :runtime
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 0.10.4
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: main
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '6.2'
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 6.2.1
|
153
|
+
type: :runtime
|
154
|
+
prerelease: false
|
155
|
+
version_requirements: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '6.2'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 6.2.1
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: sequel
|
165
|
+
requirement: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '4.39'
|
170
|
+
type: :runtime
|
171
|
+
prerelease: false
|
172
|
+
version_requirements: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - "~>"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '4.39'
|
177
|
+
- !ruby/object:Gem::Dependency
|
178
|
+
name: amalgalite
|
179
|
+
requirement: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - "~>"
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '1.5'
|
184
|
+
type: :runtime
|
185
|
+
prerelease: false
|
186
|
+
version_requirements: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - "~>"
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '1.5'
|
191
|
+
- !ruby/object:Gem::Dependency
|
192
|
+
name: color
|
193
|
+
requirement: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - "~>"
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '1.8'
|
198
|
+
type: :runtime
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - "~>"
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '1.8'
|
205
|
+
- !ruby/object:Gem::Dependency
|
206
|
+
name: paint
|
207
|
+
requirement: !ruby/object:Gem::Requirement
|
208
|
+
requirements:
|
209
|
+
- - "~>"
|
210
|
+
- !ruby/object:Gem::Version
|
211
|
+
version: '1.0'
|
212
|
+
- - ">="
|
213
|
+
- !ruby/object:Gem::Version
|
214
|
+
version: 1.0.1
|
215
|
+
type: :runtime
|
216
|
+
prerelease: false
|
217
|
+
version_requirements: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '1.0'
|
222
|
+
- - ">="
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: 1.0.1
|
225
|
+
description:
|
226
|
+
email:
|
227
|
+
- levi@leviaul.com
|
228
|
+
executables:
|
229
|
+
- docker-pier
|
230
|
+
extensions: []
|
231
|
+
extra_rdoc_files: []
|
232
|
+
files:
|
233
|
+
- ".gitignore"
|
234
|
+
- Gemfile
|
235
|
+
- README.md
|
236
|
+
- Rakefile
|
237
|
+
- bin/console
|
238
|
+
- bin/setup
|
239
|
+
- docker-pier.gemspec
|
240
|
+
- exe/docker-pier
|
241
|
+
- lib/docker-pier.rb
|
242
|
+
- lib/docker-pier/log-stream.rb
|
243
|
+
- lib/docker-pier/log-stream/palette.rb
|
244
|
+
- lib/docker-pier/node.rb
|
245
|
+
- lib/docker-pier/pier.rb
|
246
|
+
- lib/docker-pier/version.rb
|
247
|
+
- lib/docker/swarm.rb
|
248
|
+
- lib/docker/swarm/node.rb
|
249
|
+
- lib/docker/swarm/service.rb
|
250
|
+
- lib/docker/swarm/task.rb
|
251
|
+
homepage: https://github.com/tsutsu/docker-pier
|
252
|
+
licenses: []
|
253
|
+
metadata: {}
|
254
|
+
post_install_message:
|
255
|
+
rdoc_options: []
|
256
|
+
require_paths:
|
257
|
+
- lib
|
258
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
259
|
+
requirements:
|
260
|
+
- - ">="
|
261
|
+
- !ruby/object:Gem::Version
|
262
|
+
version: '0'
|
263
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
264
|
+
requirements:
|
265
|
+
- - ">="
|
266
|
+
- !ruby/object:Gem::Version
|
267
|
+
version: '0'
|
268
|
+
requirements: []
|
269
|
+
rubyforge_project:
|
270
|
+
rubygems_version: 2.5.1
|
271
|
+
signing_key:
|
272
|
+
specification_version: 4
|
273
|
+
summary: An alternative Docker client for dedicated Docker Swarm clusters
|
274
|
+
test_files: []
|