tainers 0.0.2 → 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.
@@ -1,89 +1,167 @@
|
|
1
1
|
require 'docker'
|
2
2
|
|
3
3
|
module Tainers
|
4
|
+
module Specification
|
4
5
|
|
5
|
-
|
6
|
-
|
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.
|
6
|
+
# An object representing a container configuration (a "specification"), with
|
7
|
+
# methods for checking and/or ensuring existence (by name).
|
16
8
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
20
|
-
|
9
|
+
# While this can be used directly, it is not intended for direct instantiation,
|
10
|
+
# instead designed to be used via Tainers::specify, which provides name determination
|
11
|
+
# logic for organizing containers by their configuration.
|
12
|
+
class Bare
|
13
|
+
|
14
|
+
# Creates a new container specification that uses the same parameters supported
|
15
|
+
# by the Docker::Container::create singleton method. These parameters align
|
16
|
+
# with that of the docker daemon remote API.
|
17
|
+
#
|
18
|
+
# Note that it requires a container name, and an Image. Without an Image, there's
|
19
|
+
# nothing to build. The name is essential to the purpose of the entire Tainers project.
|
20
|
+
def initialize args={}
|
21
|
+
raise ArgumentError, 'A name is required' unless valid_name? args['name']
|
22
|
+
|
23
|
+
raise ArgumentError, 'An Image is required' unless valid_image? args['Image']
|
24
|
+
|
25
|
+
# Maketh a copyeth of iteth
|
26
|
+
@args = {}.merge(args)
|
27
|
+
end
|
21
28
|
|
22
|
-
|
29
|
+
# Ensures that the container named by this specification exists, creating the
|
30
|
+
# container if necessary.
|
31
|
+
#
|
32
|
+
# Note that this only ensures that a container with the proper name exists; it does not
|
33
|
+
# ensure that the existing container has a matching configuration.
|
34
|
+
#
|
35
|
+
# Returns true if the container reliably exists (it has been shown to exist, or was
|
36
|
+
# successfully created, or failed to create due to a name conflict). All other cases
|
37
|
+
# should result in exceptions.
|
38
|
+
def ensure
|
39
|
+
return self if exists?
|
40
|
+
return self if Tainers::API.create_or_conflict(@args)
|
41
|
+
return nil
|
42
|
+
end
|
23
43
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
44
|
+
# Creates the container named by this specification, if it does
|
45
|
+
# not already exist.
|
46
|
+
#
|
47
|
+
# Returns true (self, actually) if the invocation resulted in the
|
48
|
+
# creation of a new container; false otherwise.
|
49
|
+
#
|
50
|
+
# A false condition could result from:
|
51
|
+
# - The container already existing
|
52
|
+
# - The container being simultaneously created by another
|
53
|
+
# actor, with your invocation losing the race.
|
54
|
+
#
|
55
|
+
# A failure to create due to operational or semantic issues
|
56
|
+
# should result in an exception. Therefore, any non-exceptional
|
57
|
+
# case should mean that a container of the expected name exists,
|
58
|
+
# though in the false result case there is no firm guarantee
|
59
|
+
# that the existing container has the requested configuration e.
|
60
|
+
def create
|
61
|
+
return false if exists?
|
62
|
+
return self if Tainers::API.create(@args)
|
63
|
+
false
|
64
|
+
end
|
27
65
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
66
|
+
# The name of the container described by this specification.
|
67
|
+
def name
|
68
|
+
@args['name']
|
69
|
+
end
|
70
|
+
|
71
|
+
# The image of the container described by this specification.
|
72
|
+
# Note that this is a string (a tag or image ID) and not a more
|
73
|
+
# complex object.
|
74
|
+
def image
|
75
|
+
@args['Image']
|
76
|
+
end
|
77
|
+
|
78
|
+
# True if the container of the appropriate name already exists. False if not.
|
79
|
+
def exists?
|
80
|
+
! Tainers::API.get_by_name(name).nil?
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def valid_name? name
|
86
|
+
! (name.nil? or name == '')
|
87
|
+
end
|
88
|
+
|
89
|
+
def valid_image? image
|
90
|
+
! (image.nil? or image == '')
|
91
|
+
end
|
92
|
+
end # class Bare
|
93
|
+
|
94
|
+
module Delegator
|
95
|
+
def self.delegates(method_name)
|
96
|
+
method_name = method_name.to_sym
|
97
|
+
define_method(method_name) do |*args|
|
98
|
+
chain.send(method_name, *args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def initialize(chain)
|
103
|
+
@chain = chain
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_reader :chain
|
107
|
+
|
108
|
+
delegates :create
|
109
|
+
delegates :ensure
|
110
|
+
delegates :exists?
|
111
|
+
delegates :image
|
112
|
+
delegates :name
|
41
113
|
end
|
42
114
|
|
43
|
-
#
|
44
|
-
#
|
115
|
+
# Tainer specification that automatically pulls the image
|
116
|
+
# as needed prior to container creation operations.
|
45
117
|
#
|
46
|
-
#
|
47
|
-
# creation of a new container; false otherwise.
|
118
|
+
# Wrap it around a bare specification to use it:
|
48
119
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
@args['name']
|
68
|
-
end
|
120
|
+
# t1 = Tainers::Specification::Bare.new('Image' =>' foo')
|
121
|
+
# t2 = Tainers::Specification::ImagePuller.new(t1)
|
122
|
+
# # This doesn't pull image "foo"
|
123
|
+
# t1.ensure
|
124
|
+
# # But this will, if necessary
|
125
|
+
# t2.ensure
|
126
|
+
#
|
127
|
+
# Note that the #ensure and #create methods have the pulling
|
128
|
+
# behavior; no others do.
|
129
|
+
class ImagePuller
|
130
|
+
include Delegator
|
131
|
+
|
132
|
+
def self.ensure_image(image)
|
133
|
+
if ! Tainers::API.image_exists?(image)
|
134
|
+
Tainers::API.pull_image image
|
135
|
+
end
|
136
|
+
true
|
137
|
+
end
|
69
138
|
|
70
|
-
|
71
|
-
|
72
|
-
|
139
|
+
def self.pulls_and_delegates(method_name)
|
140
|
+
method_name = method_name.to_sym
|
141
|
+
define_method(method_name) do |*args|
|
142
|
+
self.class.ensure_image(chain.image)
|
143
|
+
chain.send(method_name, *args)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
pulls_and_delegates :create
|
148
|
+
pulls_and_delegates :ensure
|
73
149
|
end
|
150
|
+
end # module Specification
|
74
151
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
152
|
+
module API
|
153
|
+
def self.image_exists? name
|
154
|
+
begin
|
155
|
+
return true if Docker::Image.get(name)
|
156
|
+
rescue Docker::Error::NotFoundError
|
157
|
+
return false
|
158
|
+
end
|
79
159
|
end
|
80
160
|
|
81
|
-
def
|
82
|
-
|
161
|
+
def self.pull_image name
|
162
|
+
Docker::Image.create('fromImage' => name)
|
83
163
|
end
|
84
|
-
end
|
85
164
|
|
86
|
-
module API
|
87
165
|
def self.get_by_name name
|
88
166
|
begin
|
89
167
|
Docker::Container.get(name)
|
@@ -105,10 +183,20 @@ module Tainers
|
|
105
183
|
create params
|
106
184
|
true
|
107
185
|
end
|
108
|
-
end
|
186
|
+
end # module API
|
109
187
|
|
188
|
+
# Returns an image-pulling container specification
|
189
|
+
# from the given parameters.
|
190
|
+
#
|
191
|
+
# Enforces the naming conventions such that the
|
192
|
+
# name for the container will have prefix, suffix,
|
193
|
+
# and spec-derived hash as documented elsewhere.
|
194
|
+
#
|
195
|
+
# The result will be an instance of Tainers::Specification::ImagePuller.
|
110
196
|
def self.specify args={}
|
111
|
-
Specification.new
|
197
|
+
Specification::ImagePuller.new(
|
198
|
+
Specification::Bare.new named_parameters_for(args)
|
199
|
+
)
|
112
200
|
end
|
113
201
|
|
114
202
|
def self.named_parameters_for params
|
@@ -22,13 +22,13 @@ shared_examples_for 'container creator' do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
|
25
|
-
RSpec.describe Tainers::Specification do
|
25
|
+
RSpec.describe Tainers::Specification::Bare do
|
26
26
|
it 'requires a name' do
|
27
|
-
expect { Tainers::Specification.new 'Image' => 'foo/image:latest' }.to raise_error(/name is required/)
|
27
|
+
expect { Tainers::Specification::Bare.new 'Image' => 'foo/image:latest' }.to raise_error(/name is required/)
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'requires an image' do
|
31
|
-
expect { Tainers::Specification.new 'name' => 'something' }.to raise_error(/Image is required/)
|
31
|
+
expect { Tainers::Specification::Bare.new 'name' => 'something' }.to raise_error(/Image is required/)
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'for a container' do
|
@@ -38,7 +38,15 @@ RSpec.describe Tainers::Specification do
|
|
38
38
|
let(:specification_args) { container_args.merge('name' => name) }
|
39
39
|
|
40
40
|
subject do
|
41
|
-
Tainers::Specification.new specification_args
|
41
|
+
Tainers::Specification::Bare.new specification_args
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'has a name' do
|
45
|
+
expect(subject.name).to equal(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'has an image' do
|
49
|
+
expect(subject.image).to equal(image)
|
42
50
|
end
|
43
51
|
|
44
52
|
context 'that does not exist' do
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rspec_helper'
|
2
|
+
|
3
|
+
shared_examples_for 'specification delegator' do |method|
|
4
|
+
let(:callable) do
|
5
|
+
subject.method(method.to_sym)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "passes through to underlying method" do
|
9
|
+
expect(wrapped).to receive(method.to_sym).with(no_args).and_return(result = double)
|
10
|
+
expect(callable.call).to be(result)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "passes arguments through to underlying method" do
|
14
|
+
args = [double, double, double]
|
15
|
+
expect(wrapped).to receive(method.to_sym).with(*args).and_return(result = double)
|
16
|
+
expect(callable.call(*args)).to be(result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
shared_examples_for 'pulling delegator' do |method|
|
21
|
+
let(:callable) do
|
22
|
+
subject.method(method.to_sym)
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:image) do
|
26
|
+
"image-#{double.to_s}-foo"
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(wrapped).to receive(:image).with(no_args).and_return(image)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "with no existing image" do
|
34
|
+
before do
|
35
|
+
expect(Docker::Image).to receive(:get).with(image).and_raise(Docker::Error::NotFoundError)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'and successful pull' do
|
39
|
+
let(:api_image) { double }
|
40
|
+
|
41
|
+
before do
|
42
|
+
expect(Docker::Image).to receive(:create).with('fromImage' => image).and_return(api_image)
|
43
|
+
end
|
44
|
+
|
45
|
+
it_behaves_like 'specification delegator', method
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'and failed pull' do
|
49
|
+
before do
|
50
|
+
# Just a general exception on this rather than a specific type;
|
51
|
+
# the docker-api gem doesn't give a pretty exception here.
|
52
|
+
expect(Docker::Image).to receive(:create).with('fromImage' => image).and_raise("Pull failed!")
|
53
|
+
expect(wrapped).to receive(method.to_sym).never
|
54
|
+
end
|
55
|
+
|
56
|
+
it "propagates the pull failure exception" do
|
57
|
+
expect { callable.call }.to raise_error("Pull failed!")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with an existing image" do
|
63
|
+
before do
|
64
|
+
expect(Docker::Image).to receive(:get).with(image).and_return(double)
|
65
|
+
expect(Docker::Image).to receive(:create).never
|
66
|
+
end
|
67
|
+
|
68
|
+
it_behaves_like 'specification delegator', method
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe Tainers::Specification::ImagePuller do
|
73
|
+
let(:wrapped) { double }
|
74
|
+
|
75
|
+
subject { Tainers::Specification::ImagePuller.new wrapped }
|
76
|
+
|
77
|
+
describe '#image method' do
|
78
|
+
it_behaves_like 'specification delegator', :image
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#name method' do
|
82
|
+
it_behaves_like 'specification delegator', :name
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#exists? method' do
|
86
|
+
it_behaves_like 'specification delegator', :exists?
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#create method' do
|
90
|
+
it_behaves_like 'pulling delegator', :create
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#ensure method' do
|
94
|
+
it_behaves_like 'pulling delegator', :ensure
|
95
|
+
end
|
96
|
+
end
|
data/spec/specify_spec.rb
CHANGED
@@ -3,7 +3,10 @@ require 'rspec_helper'
|
|
3
3
|
shared_examples_for 'a named specification' do
|
4
4
|
it 'produces a deterministically named specification' do
|
5
5
|
expect(Tainers).to receive(:hash).with(base_args.dup).and_return(hash)
|
6
|
-
|
6
|
+
# Assemble the bare specification from the parameters.
|
7
|
+
expect(Tainers::Specification::Bare).to receive(:new).with(base_args.dup.update('name' => name)).and_return(bare = double)
|
8
|
+
# And wrap it with image puller behavior so the easy case is for images to get automatically pulled down as needed.
|
9
|
+
expect(Tainers::Specification::ImagePuller).to receive(:new).with(bare).and_return(s = double)
|
7
10
|
expect(Tainers.specify specify_args).to eq(s)
|
8
11
|
end
|
9
12
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tainers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -71,12 +71,13 @@ files:
|
|
71
71
|
- lib/tainers/hash.rb
|
72
72
|
- lib/tainers.rb
|
73
73
|
- spec/hashing_spec.rb
|
74
|
+
- spec/container_specifications/bare_spec.rb
|
75
|
+
- spec/container_specifications/image_puller_spec.rb
|
74
76
|
- spec/commands/exists_spec.rb
|
75
77
|
- spec/commands/ensure_spec.rb
|
76
78
|
- spec/commands/common_examples.rb
|
77
79
|
- spec/commands/create_spec.rb
|
78
80
|
- spec/commands/name_spec.rb
|
79
|
-
- spec/specification_spec.rb
|
80
81
|
- spec/rspec_helper.rb
|
81
82
|
- spec/specify_spec.rb
|
82
83
|
- bin/tainers
|