nutkins 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 +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +10 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/nutkins +87 -0
- data/bin/setup +7 -0
- data/circle.yml +3 -0
- data/lib/nutkins/docker.rb +17 -0
- data/lib/nutkins/download.rb +35 -0
- data/lib/nutkins/version.rb +3 -0
- data/lib/nutkins.rb +207 -0
- data/nutkins.gemspec +35 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6aa69760600c24b0d1d482aa132627423d82ab80
|
4
|
+
data.tar.gz: 38fde0b125bbf671cf093bbe95c0d3b0d94e75c2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4d063d8d03c2f2930f7f019479dd2a67de7a2687e93193f4f27c2fcd513198b4f77e4521e1399ee950a07ea0c85052078ec7715fe47f1132a38a8125df304b0
|
7
|
+
data.tar.gz: 22475d2f4ba6076a2946085c34ff97afd7d74206fd616998c8c18d6877e4dfbb31dd95eab857a3857f5bbb4bf2f60445438934dbc5b9afb315c7c08ba36c3cb9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Nutkins
|
2
|
+
|
3
|
+
[](https://circleci.com/gh/ohjames/nutkins)
|
4
|
+
|
5
|
+
Nutkins is a tool to manage and test a CoreOS cluster:
|
6
|
+
* Config for each service is stored in a directory including an optional `config.yaml` and a `Dockerfile`.
|
7
|
+
* Easy to create and run test instances of any service in a single command.
|
8
|
+
* Docker images and containers are managed to avoid the difficulties using the raw `docker` command provides around removing images.
|
9
|
+
* A wrapper around [sistero](https://github.com/ohjames/sistero), a profile based cluster management tool is provided to make adding new instances to a cluster trivial (currently only digital ocean is supported).
|
10
|
+
* A wrapper around `fleetctl` to make upgrading services easy.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "nutkins"
|
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
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/nutkins
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'nutkins'
|
5
|
+
require 'moister'
|
6
|
+
require 'ostruct'
|
7
|
+
|
8
|
+
module Nutkins::Command
|
9
|
+
def self.run args
|
10
|
+
global_config = nil
|
11
|
+
command = nil
|
12
|
+
|
13
|
+
Moister::SubcommandOptionParser.new do |op|
|
14
|
+
op.banner = 'usage: nutkins [global options] command [command options]'
|
15
|
+
|
16
|
+
op.for_all do |op|
|
17
|
+
op.on_tail '-h', '--help', 'show this help message' do
|
18
|
+
puts op
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
op.on '-p', '--project dir', 'override path to project', 'project_dir'
|
24
|
+
|
25
|
+
op.subcommand 'build,b *names', 'build docker image from dockerfile'
|
26
|
+
|
27
|
+
op.subcommand 'create,c *names', 'create container from image' do |subop|
|
28
|
+
subop.on '-p', '--preserve', 'preserve existing container', 'preserve'
|
29
|
+
end
|
30
|
+
|
31
|
+
op.subcommand 'delete,d *names', 'delete container corresponding to image'
|
32
|
+
op.subcommand 'delete-all', 'delete containers corresponding to all images'
|
33
|
+
|
34
|
+
op.subcommand 'run,r name', 'run created container' do |subop|
|
35
|
+
subop.on '-r', '--reuse', 'reuse previously created container', 'reuse'
|
36
|
+
subop.on '-s', '--shell', 'add bash shell to console', 'shell'
|
37
|
+
end
|
38
|
+
|
39
|
+
op.subcommand 'exec,e name *cmd', 'execute a command in a running container'
|
40
|
+
op.subcommand 'build-secret,B path', 'build secret files/volumes'
|
41
|
+
op.subcommand 'extract-secrets,X [*names]', 'extract secret files/volumes'
|
42
|
+
|
43
|
+
parsed_cfg = op.parse(args).to_h
|
44
|
+
|
45
|
+
global_config = OpenStruct.new parsed_cfg[:config]
|
46
|
+
command = parsed_cfg[:command]
|
47
|
+
end
|
48
|
+
|
49
|
+
unless command
|
50
|
+
puts 'please supply a command, see --help'
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
nutkins = Nutkins::CloudManager.new(project_dir: global_config.project_dir)
|
55
|
+
config = OpenStruct.new global_config[command]
|
56
|
+
|
57
|
+
case command
|
58
|
+
when 'build'
|
59
|
+
names = config.names
|
60
|
+
if names.empty?
|
61
|
+
nutkins.build
|
62
|
+
else
|
63
|
+
config.names.each &nutkins.method(:build)
|
64
|
+
end
|
65
|
+
when 'create'
|
66
|
+
config.names.each do |name|
|
67
|
+
nutkins.create name, preserve: config.preserve
|
68
|
+
end
|
69
|
+
when 'delete'
|
70
|
+
config.names.each &nutkins.method(:delete)
|
71
|
+
when 'delete-all'
|
72
|
+
nutkins.delete_all
|
73
|
+
when 'run'
|
74
|
+
nutkins.run config.name, reuse: config.reuse, shell: config.shell
|
75
|
+
when 'exec'
|
76
|
+
nutkins.exec config.name, config.cmd
|
77
|
+
when 'build-secret'
|
78
|
+
nutkins.build_secret config.path
|
79
|
+
when 'extract-secrets'
|
80
|
+
nutkins.extract_secrets config.names
|
81
|
+
end
|
82
|
+
rescue RuntimeError => e
|
83
|
+
puts e.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Nutkins::Command::run ARGV
|
data/bin/setup
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Nutkins::Docker
|
2
|
+
def self.image_id_for_tag tag
|
3
|
+
regex = /^#{tag} +/
|
4
|
+
`docker images`.each_line do |line|
|
5
|
+
return line.split(' ')[2] if line =~ regex
|
6
|
+
end
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.container_id_for_tag tag
|
11
|
+
regex = /^[0-9a-f]+ +#{tag} +/
|
12
|
+
`docker ps -a`.each_line do |line|
|
13
|
+
return line.split(' ')[0] if line =~ regex
|
14
|
+
end
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Nutkins::Download
|
2
|
+
def self.download_file url, output
|
3
|
+
orig_url = url
|
4
|
+
tries = 10
|
5
|
+
while (tries -= 1) >= 0
|
6
|
+
response = Net::HTTP.get_response(URI(url))
|
7
|
+
case response
|
8
|
+
when Net::HTTPRedirection
|
9
|
+
url = response["location"]
|
10
|
+
else
|
11
|
+
open(output, "wb") do |file|
|
12
|
+
file.write(response.body)
|
13
|
+
end
|
14
|
+
return
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
raise "could not download #{orig_url}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.download_resources img_dir, resources
|
22
|
+
resources.each do |resource|
|
23
|
+
source = resource["source"]
|
24
|
+
dest = File.join(img_dir, resource["dest"])
|
25
|
+
unless File.exists? dest
|
26
|
+
FileUtils.mkdir_p File.dirname(dest)
|
27
|
+
print "downloading #{source}"
|
28
|
+
download_file source, dest
|
29
|
+
puts " - done"
|
30
|
+
mode = resource["mode"]
|
31
|
+
File.chmod(mode, dest) if mode
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/nutkins.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "ostruct"
|
3
|
+
require "net/http"
|
4
|
+
require "uri"
|
5
|
+
require "fileutils"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
module Nutkins ; end
|
9
|
+
|
10
|
+
require "nutkins/docker"
|
11
|
+
require "nutkins/download"
|
12
|
+
require "nutkins/version"
|
13
|
+
|
14
|
+
# Must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
|
15
|
+
module Nutkins
|
16
|
+
CONFIG_FILE_NAME = 'nutkins.yaml'
|
17
|
+
IMG_CONFIG_FILE_NAME = 'nutkin.yaml'
|
18
|
+
VOLUMES_PATH = 'volumes'
|
19
|
+
|
20
|
+
class CloudManager
|
21
|
+
def initialize(project_dir: nil)
|
22
|
+
@project_root = project_dir || Dir.pwd
|
23
|
+
cfg_path = File.join(@project_root, CONFIG_FILE_NAME)
|
24
|
+
if File.exists? cfg_path
|
25
|
+
@config = OpenStruct.new(YAML.load_file cfg_path)
|
26
|
+
else
|
27
|
+
@config = OpenStruct.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def build img_name = '.'
|
32
|
+
cfg = get_image_config img_name
|
33
|
+
img_dir = get_project_dir img_name
|
34
|
+
raise "directory `#{img_dir}' does not exist" unless Dir.exists? img_dir
|
35
|
+
|
36
|
+
if img_name == '.'
|
37
|
+
img_name = cfg["image"]
|
38
|
+
raise "nutkin.yaml requires image entry for `build .'" unless img_name
|
39
|
+
end
|
40
|
+
|
41
|
+
build_cfg = cfg["build"]
|
42
|
+
if build_cfg
|
43
|
+
# download each of the files in the resources section if it doesn't exist
|
44
|
+
resources = build_cfg["resources"]
|
45
|
+
Dowload.download_resources img_dir, resources if resources
|
46
|
+
end
|
47
|
+
|
48
|
+
tag = get_tag img_name
|
49
|
+
prev_image_id = Docker.image_id_for_tag tag
|
50
|
+
|
51
|
+
if run_docker "build", "-t", tag, img_dir
|
52
|
+
image_id = Docker.image_id_for_tag tag
|
53
|
+
if not prev_image_id.nil? and image_id != prev_image_id
|
54
|
+
puts "deleting previous image #{prev_image_id}"
|
55
|
+
run_docker "rmi", prev_image_id
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise "issue building docker image for #{img_name}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def create img_name, preserve: false, docker_args: []
|
63
|
+
flags = []
|
64
|
+
cfg = get_image_config img_name
|
65
|
+
create_cfg = cfg["create"]
|
66
|
+
if create_cfg
|
67
|
+
(create_cfg["ports"] or []).each do |port|
|
68
|
+
flags.push '-p', "#{port}:#{port}"
|
69
|
+
end
|
70
|
+
|
71
|
+
img_dir = get_project_dir img_name
|
72
|
+
(create_cfg["volumes"] or []).each do |volume|
|
73
|
+
src, dest = volume.split ' -> '
|
74
|
+
src = File.absolute_path File.join(img_dir, VOLUMES_PATH, src)
|
75
|
+
flags.push '-v', "#{src}:#{dest}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
tag = get_tag img_name
|
80
|
+
prev_container_id = Docker.container_id_for_tag tag unless preserve
|
81
|
+
puts "creating new docker image"
|
82
|
+
unless run_docker "create", "-it", *flags, tag, *docker_args
|
83
|
+
raise "failed to create `#{img_name}' container"
|
84
|
+
end
|
85
|
+
|
86
|
+
unless preserve
|
87
|
+
container_id = Docker.container_id_for_tag tag
|
88
|
+
if not prev_container_id.nil? and container_id != prev_container_id
|
89
|
+
puts "deleting previous container #{prev_container_id}"
|
90
|
+
run_docker "rm", prev_container_id
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
puts "created `#{img_name}' container"
|
95
|
+
end
|
96
|
+
|
97
|
+
def run img_name, reuse: false, shell: false
|
98
|
+
cfg = get_image_config img_name
|
99
|
+
tag = get_tag img_name
|
100
|
+
create_args = []
|
101
|
+
if shell
|
102
|
+
raise '--shell and --reuse arguments are incompatible' if reuse
|
103
|
+
|
104
|
+
# TODO: test for smell-baron
|
105
|
+
create_args = JSON.parse(`docker inspect #{tag}`)[0]["Config"]["Cmd"]
|
106
|
+
create_args.unshift '/bin/bash', '---'
|
107
|
+
create_args.unshift '-f' unless create_args[0] == '-f'
|
108
|
+
# TODO: provide version that doesn't require smell-baron
|
109
|
+
end
|
110
|
+
|
111
|
+
id = reuse && Docker.container_id_for_tag(tag)
|
112
|
+
unless id
|
113
|
+
create img_name, docker_args: create_args
|
114
|
+
id = Docker.container_id_for_tag tag
|
115
|
+
raise "couldn't create container to run `#{img_name}'" unless id
|
116
|
+
end
|
117
|
+
|
118
|
+
Kernel.exec "docker", "start", "-ai", id
|
119
|
+
end
|
120
|
+
|
121
|
+
def delete img_name
|
122
|
+
puts "TODO: delete #{img_name}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def delete_all
|
126
|
+
puts "TODO: delete_all"
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_secret path
|
130
|
+
secret = path
|
131
|
+
path_is_dir = Dir.exists? path
|
132
|
+
if path_is_dir
|
133
|
+
secret += '.tar'
|
134
|
+
system "tar", "cf", secret, "-C", File.dirname(path), File.basename(path)
|
135
|
+
end
|
136
|
+
|
137
|
+
loop do
|
138
|
+
puts "enter passphrase for #{secret}"
|
139
|
+
break if system 'gpg', '-c', secret
|
140
|
+
end
|
141
|
+
|
142
|
+
File.unlink secret if path_is_dir
|
143
|
+
end
|
144
|
+
|
145
|
+
def extract_secrets img_names
|
146
|
+
with_current = img_names.empty?
|
147
|
+
img_names = get_image_names(img_names)
|
148
|
+
img_names.push '.' if with_current
|
149
|
+
|
150
|
+
img_names.each do |img_name|
|
151
|
+
get_secrets(img_name).each do |secret|
|
152
|
+
loop do
|
153
|
+
puts "enter passphrase for #{secret}"
|
154
|
+
break if system 'gpg', secret
|
155
|
+
end
|
156
|
+
|
157
|
+
secret = secret[0..-5]
|
158
|
+
if File.extname(secret) == '.tar'
|
159
|
+
system "tar", "xf", secret, "-C", File.dirname(secret)
|
160
|
+
File.unlink secret
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def exec img_name, *cmd
|
167
|
+
puts "TODO: exec #{img_name}: #{cmd.join ' '}"
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
def get_image_config path
|
172
|
+
img_cfg_path = File.join get_project_dir(path), IMG_CONFIG_FILE_NAME
|
173
|
+
img_cfg = File.exists?(img_cfg_path) ? YAML.load_file(img_cfg_path) : {}
|
174
|
+
@repository = img_cfg['repository'] || @config.repository
|
175
|
+
img_cfg
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_project_dir path
|
179
|
+
path == '.' ? @project_root : File.join(@project_root, path)
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_tag tag
|
183
|
+
raise "command requires `repository' entry in nutkins.yaml or nutkin.yaml" if @repository.nil?
|
184
|
+
@repository + '/' + tag
|
185
|
+
end
|
186
|
+
|
187
|
+
def get_image_names img_names
|
188
|
+
if img_names.empty?
|
189
|
+
Dir.glob("#{@project_root}/*/Dockerfile").map do |path|
|
190
|
+
File.basename File.dirname(path)
|
191
|
+
end
|
192
|
+
else
|
193
|
+
img_names
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# can supply img_name or . for project root
|
198
|
+
def get_secrets img_name
|
199
|
+
img_dir = get_project_dir img_name
|
200
|
+
Dir.glob("#{img_dir}/{volumes,secrets}/*.gpg")
|
201
|
+
end
|
202
|
+
|
203
|
+
def run_docker *args
|
204
|
+
system 'docker', *args
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/nutkins.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nutkins/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "nutkins"
|
8
|
+
spec.version = Nutkins::VERSION
|
9
|
+
spec.authors = ["James Pike"]
|
10
|
+
spec.email = ["github@chilon.net"]
|
11
|
+
|
12
|
+
spec.summary = %q{CoreOS cluster management tool.}
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "http://github.com/ohjames/nutkins"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
17
|
+
# delete this section to allow pushing this gem to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
20
|
+
else
|
21
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec"
|
32
|
+
|
33
|
+
spec.add_dependency "sistero", "~> 0.4.4"
|
34
|
+
spec.add_dependency "moister", "~> 0.3.0"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nutkins
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Pike
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-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.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sistero
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.4.4
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.4.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: moister
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.3.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.3.0
|
83
|
+
description: CoreOS cluster management tool.
|
84
|
+
email:
|
85
|
+
- github@chilon.net
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/nutkins
|
98
|
+
- bin/setup
|
99
|
+
- circle.yml
|
100
|
+
- lib/nutkins.rb
|
101
|
+
- lib/nutkins/docker.rb
|
102
|
+
- lib/nutkins/download.rb
|
103
|
+
- lib/nutkins/version.rb
|
104
|
+
- nutkins.gemspec
|
105
|
+
homepage: http://github.com/ohjames/nutkins
|
106
|
+
licenses: []
|
107
|
+
metadata:
|
108
|
+
allowed_push_host: https://rubygems.org
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.5.1
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: CoreOS cluster management tool.
|
129
|
+
test_files: []
|