tainers 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'docker-api', '~> 1.15.0'
4
+
5
+ group(:development, :test) do
6
+ gem 'rspec', '~> 3.1.0'
7
+ end
8
+
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ archive-tar-minitar (0.5.2)
5
+ diff-lcs (1.2.5)
6
+ docker-api (1.15.0)
7
+ archive-tar-minitar
8
+ excon (>= 0.38.0)
9
+ json
10
+ excon (0.41.0)
11
+ json (1.8.1)
12
+ rspec (3.1.0)
13
+ rspec-core (~> 3.1.0)
14
+ rspec-expectations (~> 3.1.0)
15
+ rspec-mocks (~> 3.1.0)
16
+ rspec-core (3.1.7)
17
+ rspec-support (~> 3.1.0)
18
+ rspec-expectations (3.1.2)
19
+ diff-lcs (>= 1.2.0, < 2.0)
20
+ rspec-support (~> 3.1.0)
21
+ rspec-mocks (3.1.3)
22
+ rspec-support (~> 3.1.0)
23
+ rspec-support (3.1.2)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ docker-api (~> 1.15.0)
30
+ rspec (~> 3.1.0)
data/bin/tainers ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # vim: set filetype=ruby:
4
+
5
+ require 'tainers'
6
+
7
+ exit Tainers::CLI.run(ARGV)
data/lib/tainers.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'digest'
2
+ require 'json'
3
+
4
+ module Tainers
5
+ end
6
+
7
+ require 'tainers/cli'
8
+ require 'tainers/hash'
9
+ require 'tainers/specification'
@@ -0,0 +1,128 @@
1
+ require 'optparse'
2
+ require 'json'
3
+
4
+ module Tainers
5
+ module CLI
6
+ def self.run parameters
7
+ spec, parameters = parse(parameters)
8
+ cmd = Command.new(spec)
9
+ cmd_name = parameters.shift
10
+ cmd.send("#{cmd_name}_command".to_sym, *parameters)
11
+ end
12
+
13
+ def self.parse parameters
14
+ options = {}
15
+ options[:spec_source] = from_stdin
16
+ opt_parser = OptionParser.new do |opts|
17
+ opts.banner = "Usage: tainers [opts] COMMAND"
18
+ opts.separator ""
19
+ opts.separator "Manipulate Tainer-managed containers, taking their specification in JSON (from STDIN by default)."
20
+ opts.separator ""
21
+ opts.separator "Specific options:"
22
+
23
+ opts.on('-j JSON', '--json JSON', String, "Take container specification from the given JSON parameter.") do |j|
24
+ options[:spec_source] = from_json(j)
25
+ end
26
+
27
+ opts.on('-f FILEPATH', '--file FILEPATH', String, "Take container specification from JSON in the given file.") do |f|
28
+ options[:spec_source] = from_file(f)
29
+ end
30
+
31
+ opts.on('-p PREFIX', '--prefix PREFIX', String, "Use PREFIX as container name prefix (overriding whatever is in spec)") do |p|
32
+ options['prefix'] = p
33
+ end
34
+
35
+ opts.on('-s SUFFIX', '--suffix SUFFIX', String, "Use SUFFIX as container name suffix (overriding whatever is in spec)") do |s|
36
+ options['suffix'] = s
37
+ end
38
+
39
+ opts.on('-h', '--help') do
40
+ print opts
41
+ exit 0
42
+ end
43
+
44
+ opts.separator ""
45
+ opts.separator "Commands:"
46
+ Command.commands.each do |name, help|
47
+ opts.separator ""
48
+ opts.separator " #{name}"
49
+ opts.separator " #{help}" if help.size > 0
50
+ end
51
+ end
52
+
53
+ # Stop on first non-option param
54
+ non_param = []
55
+ args = opt_parser.order(parameters) {|p| non_param << p; opt_parser.terminate}
56
+ spec = options.delete(:spec_source).call
57
+ spec.update(options)
58
+ [spec, non_param + args]
59
+ end
60
+
61
+ def self.from_stdin
62
+ Proc.new do
63
+ JSON.parse(STDIN.read)
64
+ end
65
+ end
66
+
67
+ def self.from_file file
68
+ Proc.new do
69
+ File.open(file, "r") do |f|
70
+ JSON.parse(f.read)
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.from_json json
76
+ Proc.new do
77
+ JSON.parse json
78
+ end
79
+ end
80
+
81
+ class Command
82
+ attr_reader :specification
83
+
84
+ def initialize(spec={})
85
+ @specification = Tainers.specify(spec)
86
+ end
87
+
88
+ def self.commands
89
+ cmds = instance_methods.collect {|m| m.to_s}.find_all {|m| m.to_s =~ /_command$/}.collect {|n| n.to_s[0..-9] }
90
+ cmds.collect do |name|
91
+ helper = "#{name}_help".to_sym
92
+ if respond_to? helper
93
+ [name, send(helper)]
94
+ else
95
+ [name, '']
96
+ end
97
+ end
98
+ end
99
+
100
+ def ensure_command
101
+ return 0 if specification.ensure
102
+ 255
103
+ end
104
+
105
+ def self.ensure_help
106
+ "Ensures specified container exists, by name."
107
+ end
108
+
109
+ def exists_command
110
+ return 0 if specification.exists?
111
+ 1
112
+ end
113
+
114
+ def self.exists_help
115
+ "Exits with 0 (true) if specified container exists, by name; 1 (false) if not."
116
+ end
117
+
118
+ def name_command
119
+ STDOUT.print "#{specification.name}\n"
120
+ 0
121
+ end
122
+
123
+ def self.name_help
124
+ "Prints the specified container's name on stdout."
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,44 @@
1
+ module Tainers
2
+ # call-seq:
3
+ # Tainers.hash(structure) => consistent hash hexdigest
4
+ #
5
+ # Produces a consistent hash hexdigest string for the input
6
+ # structure, handling arrays, hashes, strings, numbers, nesting
7
+ # of such structures, etc.
8
+ #
9
+ # Hash keys are sorted prior to computing the digest, to ensure
10
+ # that hashes that would be regarded as equal produce the same
11
+ # digest.
12
+ #
13
+ # Tainers.hash({"a" => "foo", "b" => "bar"}) #=> "5427f704439548cae1911616e2bec3b7cc2dd11c"
14
+ #
15
+ def self.hash structure
16
+ str = structured_string(structure)
17
+ Digest::SHA1.hexdigest str
18
+ end
19
+
20
+ private
21
+
22
+ def self.consistent_structure structure
23
+ if Hash === structure
24
+ s = structure.keys.sort.inject(["{"]) do |a, key|
25
+ a << consistent_structure(key)
26
+ a << consistent_structure(structure[key])
27
+ a
28
+ end
29
+ s << "}"
30
+ s
31
+ elsif Array === structure
32
+ s = structure.collect {|item| consistent_structure item}
33
+ s.unshift "["
34
+ s << "]"
35
+ s
36
+ else
37
+ structure
38
+ end
39
+ end
40
+
41
+ def self.structured_string structure
42
+ consistent_structure(structure).to_json
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ require 'docker'
2
+
3
+ module Tainers
4
+
5
+ # An object representing a container configuration (a "specification"), with
6
+ # methods for checking and/or ensuring existence (by name).
7
+ #
8
+ # While this can be used directly, it is not intended for direct instantiation,
9
+ # instead designed to be used via Tainers::specify, which provides name determination
10
+ # logic for organizing containers by their configuration.
11
+ class Specification
12
+
13
+ # Creates a new container specification that uses the same parameters supported
14
+ # by the Docker::Container::create singleton method. These parameters align
15
+ # with that of the docker daemon remote API.
16
+ #
17
+ # Note that it requires a container name, and an Image. Without an Image, there's
18
+ # nothing to build. The name is essential to the purpose of the entire Tainers project.
19
+ def initialize args={}
20
+ raise ArgumentError, 'A name is required' unless valid_name? args['name']
21
+
22
+ raise ArgumentError, 'An Image is required' unless valid_image? args['Image']
23
+
24
+ # Maketh a copyeth of iteth
25
+ @args = {}.merge(args)
26
+ end
27
+
28
+ # Ensures that the container named by this specification exists, creating the
29
+ # container if necessary.
30
+ #
31
+ # Note that this only ensures that a container with the proper name exists; it does not
32
+ # ensure that the existing container has a matching configuration.
33
+ #
34
+ # Returns true if the container reliably exists (it has been shown to exist, or was
35
+ # successfully created, or failed to create due to a name conflict). All other cases
36
+ # should result in exceptions.
37
+ def ensure
38
+ return self if exists?
39
+ return self if Tainers::API.create_or_conflict(@args)
40
+ return nil
41
+ end
42
+
43
+ # The name of the container described by this specification.
44
+ def name
45
+ @args['name']
46
+ end
47
+
48
+ # True if the container of the appropriate name already exists. False if not.
49
+ def exists?
50
+ ! Tainers::API.get_by_name(name).nil?
51
+ end
52
+
53
+ private
54
+
55
+ def valid_name? name
56
+ ! (name.nil? or name == '')
57
+ end
58
+
59
+ def valid_image? image
60
+ ! (image.nil? or image == '')
61
+ end
62
+ end
63
+
64
+ module API
65
+ def self.get_by_name name
66
+ begin
67
+ Docker::Container.get(name)
68
+ rescue Docker::Error::NotFoundError
69
+ nil
70
+ end
71
+ end
72
+
73
+ def self.create_or_conflict params
74
+ begin
75
+ Docker::Container.create(params.dup)
76
+ return true
77
+ rescue Excon::Errors::Conflict
78
+ return true
79
+ end
80
+ false
81
+ end
82
+ end
83
+
84
+ def self.specify args={}
85
+ Specification.new named_parameters_for(args)
86
+ end
87
+
88
+ def self.named_parameters_for params
89
+ p = params.dup
90
+ prefix = p.delete('prefix')
91
+ prefix = if prefix.nil? || prefix == ''
92
+ 'Tainers'
93
+ else
94
+ prefix.downcase
95
+ end
96
+ suffix = p.delete('suffix')
97
+ suffix = if suffix.nil?
98
+ ''
99
+ else
100
+ "-#{suffix.downcase}"
101
+ end
102
+ digest = hash(p)
103
+ p['name'] = "#{prefix}-#{digest}#{suffix}"
104
+ p
105
+ end
106
+ end
@@ -0,0 +1,129 @@
1
+ require 'rspec_helper'
2
+ require 'json'
3
+ require 'tempfile'
4
+
5
+ shared_examples_for 'prefix and suffix' do |command_group|
6
+ let(:param_prefix) { "opt-" + double.object_id.to_s }
7
+ let(:param_suffix) { "opt-" + double.object_id.to_s }
8
+
9
+ context 'with --prefix' do
10
+ before do
11
+ args << '--prefix'
12
+ args << param_prefix
13
+ expected_specification['prefix'] = param_prefix
14
+ end
15
+
16
+ it_behaves_like command_group
17
+
18
+ context 'and --suffix' do
19
+ before do
20
+ args << '--suffix'
21
+ args << param_suffix
22
+ expected_specification['suffix'] = param_suffix
23
+ end
24
+
25
+ it_behaves_like command_group
26
+ end
27
+ end
28
+
29
+ context 'with --suffix' do
30
+ before do
31
+ args << '--suffix'
32
+ args << param_suffix
33
+ expected_specification['suffix'] = param_suffix
34
+ end
35
+
36
+ it_behaves_like command_group
37
+ end
38
+ end
39
+
40
+ shared_examples_for 'a command' do |command_group|
41
+ let :provided_specification do
42
+ {
43
+ 'Image' => double.object_id.to_s,
44
+ double.to_s => double.to_s,
45
+ 'prefix' => "spec-" + double.object_id.to_s,
46
+ 'suffix' => "spec-" + double.object_id.to_s
47
+ }
48
+ end
49
+
50
+ let :expected_specification do
51
+ provided_specification.dup
52
+ end
53
+
54
+ let(:args) { [] }
55
+
56
+ let(:json) { provided_specification.to_json }
57
+
58
+ let(:specification) { double }
59
+
60
+ let :command_run do
61
+ Tainers::CLI.run args
62
+ end
63
+
64
+ before do
65
+ expect(Tainers).to receive(:specify).with(expected_specification).and_return(specification)
66
+ end
67
+
68
+ context 'given JSON specification in parameters' do
69
+ context 'via -j' do
70
+ before do
71
+ args << '-j'
72
+ args << json
73
+ end
74
+
75
+ it_behaves_like "prefix and suffix", command_group
76
+ end
77
+
78
+ context 'via --json' do
79
+ before do
80
+ args << '--json'
81
+ args << json
82
+ end
83
+
84
+ it_behaves_like "prefix and suffix", command_group
85
+ end
86
+ end
87
+
88
+ context 'given JSON specification in a file' do
89
+ let(:file) do
90
+ f = Tempfile.new('tainers-test-spec')
91
+ f.write(json)
92
+ f.close
93
+ f
94
+ end
95
+
96
+ let(:path) { file.path }
97
+
98
+ after do
99
+ file.unlink
100
+ end
101
+
102
+ context 'via -f' do
103
+ before do
104
+ args << '-f'
105
+ args << path
106
+ end
107
+
108
+ it_behaves_like "prefix and suffix", command_group
109
+ end
110
+
111
+ context 'via --file' do
112
+ before do
113
+ args << '--file'
114
+ args << path
115
+ end
116
+
117
+ it_behaves_like "prefix and suffix", command_group
118
+ end
119
+ end
120
+
121
+ context 'given JSON specification on STDIN' do
122
+ before do
123
+ expect(STDIN).to receive(:read).and_return(json)
124
+ end
125
+
126
+ it_behaves_like "prefix and suffix", command_group
127
+ end
128
+ end
129
+
@@ -0,0 +1,17 @@
1
+ require_relative 'common_examples'
2
+
3
+ shared_examples_for 'ensure command' do
4
+ before do
5
+ args << 'ensure'
6
+ end
7
+
8
+ it "ensures container and exits appropriately" do
9
+ expect(specification).to receive(:ensure).with(no_args).and_return(true)
10
+ expect(command_run).to eq(0)
11
+ end
12
+ end
13
+
14
+ describe 'tainers ensure' do
15
+ it_behaves_like "a command", "ensure command"
16
+ end
17
+
@@ -0,0 +1,22 @@
1
+ require_relative 'common_examples'
2
+
3
+ shared_examples_for 'exists command' do
4
+ before do
5
+ args << 'exists'
6
+ end
7
+
8
+ it 'returns 0 for an existing container' do
9
+ expect(specification).to receive(:exists?).with(no_args).and_return(true)
10
+ expect(command_run).to eq(0)
11
+ end
12
+
13
+ it 'returns 1 for a non-existent container' do
14
+ expect(specification).to receive(:exists?).with(no_args).and_return(false)
15
+ expect(command_run).to eq(1)
16
+ end
17
+ end
18
+
19
+ describe 'tainers exists' do
20
+ it_behaves_like 'a command', 'exists command'
21
+ end
22
+
@@ -0,0 +1,18 @@
1
+ require_relative 'common_examples'
2
+
3
+ shared_examples_for 'name command' do
4
+ before do
5
+ args << 'name'
6
+ end
7
+
8
+ it "writes the name of the container to stdout" do
9
+ expect(specification).to receive(:name).with(no_args).and_return(name = "some-name-" + double.object_id.to_s)
10
+ expect(STDOUT).to receive(:print).with("#{name}\n")
11
+ expect(command_run).to eq(0)
12
+ end
13
+ end
14
+
15
+ describe 'tainers name' do
16
+ it_behaves_like 'a command', 'name command'
17
+ end
18
+
@@ -0,0 +1,87 @@
1
+ require 'rspec_helper'
2
+ require 'digest'
3
+
4
+ def hashit str
5
+ Digest::SHA1.hexdigest(str)
6
+ end
7
+
8
+ RSpec.describe 'Tainers.hash' do
9
+ let(:test_number) { self.object_id }
10
+ let(:test_string) { "something-#{self.object_id.to_s}-blah" }
11
+
12
+ it 'handles a simple string' do
13
+ expect(Tainers.hash test_string).to eq(hashit('"' + test_string + '"'))
14
+ end
15
+
16
+ it 'handles a simple number' do
17
+ expect(Tainers.hash test_number).to eq(hashit(test_number.to_s))
18
+ end
19
+
20
+ it 'handles an array of mixed simple type' do
21
+ expected_ary = ['[', test_string, test_number, ']'].to_json
22
+ expect(Tainers.hash [test_string, test_number]).to eq(hashit expected_ary)
23
+ end
24
+
25
+ it 'handles a hash of mixed simple type' do
26
+ expected = ["{", "a", test_number, "b", test_string, "c", test_number + 1, "d", test_string + "z", "}"].to_json
27
+ expect(Tainers.hash "c" => test_number + 1,
28
+ "a" => test_number,
29
+ "d" => test_string + "z",
30
+ "b" => test_string).to eq(hashit expected)
31
+ end
32
+
33
+ it 'handles a complex structure' do
34
+ expected = [
35
+ "[",
36
+ test_number,
37
+ [
38
+ '{',
39
+ 'first', [
40
+ '{',
41
+ 1, 'one',
42
+ 2, 'two',
43
+ '}',
44
+ ],
45
+ 'second', [
46
+ '[',
47
+ 'a',
48
+ 'b',
49
+ ']',
50
+ ],
51
+ '}',
52
+ ],
53
+ test_string,
54
+ [
55
+ '{',
56
+ 'fourth', [
57
+ '[',
58
+ 1000,
59
+ 500,
60
+ ']',
61
+ ],
62
+ 'third', [
63
+ '{',
64
+ ['[', 'a', 'b', ']'], 'ey',
65
+ ['[', 'b', 'a', ']'], 'bee',
66
+ '}',
67
+ ],
68
+ '}',
69
+ ],
70
+ "]",
71
+ ].to_json
72
+ struct = [
73
+ test_number,
74
+ {
75
+ 'first' => {2 => 'two', 1 => 'one'},
76
+ 'second' => ['a', 'b'],
77
+ },
78
+ test_string,
79
+ {
80
+ 'third' => {['b','a'] => 'bee', ['a','b'] => 'ey'},
81
+ 'fourth' => [1000, 500],
82
+ },
83
+ ]
84
+ expect(Tainers.hash struct).to eq(hashit expected)
85
+ end
86
+ end
87
+
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+ require 'tainers'
3
+
@@ -0,0 +1,63 @@
1
+ require 'rspec_helper'
2
+
3
+ RSpec.describe Tainers::Specification do
4
+ it 'requires a name' do
5
+ expect { Tainers::Specification.new 'Image' => 'foo/image:latest' }.to raise_error(/name is required/)
6
+ end
7
+
8
+ it 'requires an image' do
9
+ expect { Tainers::Specification.new 'name' => 'something' }.to raise_error(/Image is required/)
10
+ end
11
+
12
+ context 'for a container' do
13
+ let(:name) { "something-#{object_id.to_s}-foo" }
14
+ let(:image) { "some.#{object_id.to_s}.repo:5000/some-image/#{object_id.to_s[0..5]}" }
15
+ let(:container_args) { {'Image' => image, double.to_s => double.to_s } }
16
+ let(:specification_args) { container_args.merge('name' => name) }
17
+
18
+ subject do
19
+ Tainers::Specification.new specification_args
20
+ end
21
+
22
+ context 'that does not exist' do
23
+ it 'indicates non-existence' do
24
+ expect(Docker::Container).to receive(:get).with(name).and_raise(Docker::Error::NotFoundError)
25
+ expect(subject.exists?).to be false
26
+ end
27
+
28
+ context '#ensure' do
29
+ before do
30
+ expect(subject).to receive(:exists?).and_return(false)
31
+ end
32
+
33
+ it 'uses Docker::Container for creation' do
34
+ expect(Docker::Container).to receive(:create).with(specification_args).and_return(container = double)
35
+ expect(subject.ensure).to be(subject)
36
+ end
37
+
38
+ it 'is okay with a conflict result' do
39
+ expect(Docker::Container).to receive(:create).with(specification_args).and_raise(Excon::Errors::Conflict, "Pickles")
40
+ expect(subject.ensure).to be(subject)
41
+ end
42
+
43
+ it 'does not handle other exceptions' do
44
+ expect(Docker::Container).to receive(:create).with(specification_args).and_raise(Exception, "You suck.")
45
+ expect { subject.ensure }.to raise_error("You suck.")
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'that exists' do
51
+ it 'indicates existence' do
52
+ expect(Docker::Container).to receive(:get).with(name).and_return(double)
53
+ expect(subject.exists?).to be true
54
+ end
55
+
56
+ it 'does a no-op for #ensure' do
57
+ expect(subject).to receive(:exists?).and_return(true)
58
+ expect(Docker::Container).to receive(:create).never
59
+ expect(subject.ensure).to be(subject)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,79 @@
1
+ require 'rspec_helper'
2
+
3
+ shared_examples_for 'a named specification' do
4
+ it 'produces a deterministically named specification' do
5
+ expect(Tainers).to receive(:hash).with(base_args.dup).and_return(hash)
6
+ expect(Tainers::Specification).to receive(:new).with(base_args.dup.update('name' => name)).and_return(s = double)
7
+ expect(Tainers.specify specify_args).to eq(s)
8
+ end
9
+ end
10
+
11
+ describe 'Tainers::specify' do
12
+ let(:prefix) { nil }
13
+ let(:suffix) { nil }
14
+
15
+ let(:base_args) { { double.to_s => double.to_s, double.to_s => double.to_s } }
16
+
17
+ let(:specify_args) {
18
+ [['prefix', prefix], ['suffix', suffix]].inject(base_args.dup) do |a, item|
19
+ if item[1].nil?
20
+ a
21
+ else
22
+ a.merge(item[0] => item[1])
23
+ end
24
+ end
25
+ }
26
+
27
+ let(:hash) { double.object_id.to_s }
28
+
29
+ let(:name) {
30
+ pre = if prefix.nil?
31
+ 'Tainers'
32
+ else
33
+ prefix.downcase
34
+ end
35
+ pre = 'Tainers' if pre.nil?
36
+ suf = if suffix.nil?
37
+ ''
38
+ else
39
+ '-' + suffix.downcase
40
+ end
41
+ "#{pre}-#{hash}#{suf}"
42
+ }
43
+
44
+ context 'with a prefix' do
45
+ let(:prefix) { "pre#{double.object_id.to_s[0..5]}" }
46
+
47
+ it_behaves_like 'a named specification'
48
+
49
+ context 'of mixed case' do
50
+ let(:prefix) { "PrE#{double.object_id.to_s[0..5]}" }
51
+
52
+ it_behaves_like 'a named specification'
53
+
54
+ context 'and a suffix' do
55
+ let(:suffix) { "#{double.object_id.to_s[0..5]}sfx" }
56
+
57
+ it_behaves_like 'a named specification'
58
+
59
+ context 'of mixed case' do
60
+ let(:suffix) { "#{double.object_id.to_s[0..5]}SfX" }
61
+
62
+ it_behaves_like 'a named specification'
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'with a suffix' do
69
+ let(:suffix) { "#{double.object_id.to_s[1..6]}suffy" }
70
+
71
+ it_behaves_like 'a named specification'
72
+
73
+ context 'of mixed case' do
74
+ let(:suffix) { "#{double.object_id.to_s[1..6]}sUFFy" }
75
+
76
+ it_behaves_like 'a named specification'
77
+ end
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tainers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ethan Rowe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-11-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: docker-api
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.15.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.15.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.7.6
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.7.6
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 3.1.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 3.1.0
62
+ description: Config-driven management of docker containers
63
+ email: ethan@the-rowes.com
64
+ executables:
65
+ - tainers
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - lib/tainers/cli.rb
70
+ - lib/tainers/specification.rb
71
+ - lib/tainers/hash.rb
72
+ - lib/tainers.rb
73
+ - spec/hashing_spec.rb
74
+ - spec/commands/exists_spec.rb
75
+ - spec/commands/ensure_spec.rb
76
+ - spec/commands/common_examples.rb
77
+ - spec/commands/name_spec.rb
78
+ - spec/specification_spec.rb
79
+ - spec/rspec_helper.rb
80
+ - spec/specify_spec.rb
81
+ - bin/tainers
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ homepage: http://github.com/ethanrowe/ruby-tainers
85
+ licenses:
86
+ - MIT
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.23
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Manage docker containers based on deterministic naming derived from container
109
+ configuration
110
+ test_files: []