tainers 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []