twistlock-control 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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +6 -0
- data/Gemfile +16 -0
- data/Guardfile +30 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/features/provisioning_service_instances.feature +14 -0
- data/features/step_definitions/provisioning_service_instances_steps.rb +33 -0
- data/features/support/env.rb +22 -0
- data/lib/twistlock_control.rb +65 -0
- data/lib/twistlock_control/actions.rb +7 -0
- data/lib/twistlock_control/actions/container.rb +42 -0
- data/lib/twistlock_control/actions/container_instance.rb +28 -0
- data/lib/twistlock_control/actions/provisioner.rb +25 -0
- data/lib/twistlock_control/actions/service.rb +18 -0
- data/lib/twistlock_control/actions/service_instance.rb +40 -0
- data/lib/twistlock_control/collections.rb +22 -0
- data/lib/twistlock_control/entities.rb +11 -0
- data/lib/twistlock_control/entities/composite_service.rb +86 -0
- data/lib/twistlock_control/entities/container.rb +66 -0
- data/lib/twistlock_control/entities/container_instance.rb +22 -0
- data/lib/twistlock_control/entities/provisioner.rb +24 -0
- data/lib/twistlock_control/entities/provisioning_configuration.rb +65 -0
- data/lib/twistlock_control/entities/service.rb +19 -0
- data/lib/twistlock_control/entities/service_instance.rb +122 -0
- data/lib/twistlock_control/entity.rb +63 -0
- data/lib/twistlock_control/provisioner_api.rb +43 -0
- data/lib/twistlock_control/rethinkdb_repository.rb +74 -0
- data/lib/twistlock_control/version.rb +4 -0
- data/spec/actions/container_spec.rb +19 -0
- data/spec/actions/provisioner_spec.rb +37 -0
- data/spec/actions/service_instance_spec.rb +47 -0
- data/spec/collections_spec.rb +14 -0
- data/spec/entities/composite_service_spec.rb +126 -0
- data/spec/entities/container_spec.rb +8 -0
- data/spec/entities/provisioner_spec.rb +56 -0
- data/spec/entities/service_instance_spec.rb +33 -0
- data/spec/entities/shared_service_specs.rb +4 -0
- data/spec/provisioner_api_spec.rb +35 -0
- data/spec/rethinkdb_repository_spec.rb +15 -0
- data/spec/spec_helper.rb +25 -0
- data/twistlock-control.gemspec +29 -0
- metadata +172 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
module TwistlockControl
|
4
|
+
# An entity is basically a struct with typed fields.
|
5
|
+
class Entity
|
6
|
+
include Virtus.model
|
7
|
+
|
8
|
+
def ==(other)
|
9
|
+
return false unless other.respond_to? :attributes
|
10
|
+
attributes == other.attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize
|
14
|
+
attributes.dup
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# A persisted entity is an entity that has its own persistant
|
19
|
+
# storage repository.
|
20
|
+
class PersistedEntity < Entity
|
21
|
+
def self.find_by_id(id)
|
22
|
+
deserialize repository.find_by_id(id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.deserialize(attrs)
|
26
|
+
return nil if attrs.nil?
|
27
|
+
new(attrs)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_with_ids(ids)
|
31
|
+
repository.find_with_ids(ids).map { |a| deserialize a }
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.all
|
35
|
+
repository.all.map { |a| deserialize a }
|
36
|
+
end
|
37
|
+
|
38
|
+
def save
|
39
|
+
repository.save(serialize)
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove
|
43
|
+
repository.remove(id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def repository
|
47
|
+
self.class.repository
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.inherited(subclass)
|
51
|
+
subclass.class_exec do
|
52
|
+
def self.repository(repository = nil)
|
53
|
+
return @repository = repository if repository
|
54
|
+
return @repository if @repository
|
55
|
+
return superclass.repository if superclass.respond_to?(:repository)
|
56
|
+
|
57
|
+
fail "#{name} has not defined a repository."
|
58
|
+
end
|
59
|
+
end
|
60
|
+
super(subclass)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module TwistlockControl
|
5
|
+
# The provisioner api provides a direct synchronous interface to a provisioner
|
6
|
+
# at the url it is initialized with.
|
7
|
+
class ProvisionerAPI
|
8
|
+
attr_reader :url
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
@url = url
|
12
|
+
end
|
13
|
+
|
14
|
+
def provision_container(container_configuration)
|
15
|
+
container = container_configuration.container
|
16
|
+
add_container(container.name, container.url)
|
17
|
+
JSON.parse post('containers', name: container.name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def container_description(name)
|
21
|
+
JSON.parse get("templates/#{name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_container(name, url)
|
25
|
+
JSON.parse post('templates', name: name, url: url)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def uri(path)
|
31
|
+
URI(url + '/' + path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(path)
|
35
|
+
Net::HTTP.get uri(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def post(path, params)
|
39
|
+
result = Net::HTTP.post_form uri(path), params
|
40
|
+
result.body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module TwistlockControl
|
2
|
+
# Some helper functions around access to RethinkDB collections
|
3
|
+
class RethinkDBRepository
|
4
|
+
def self.[](table_name)
|
5
|
+
new(table_name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(table_name)
|
9
|
+
@table_name = table_name
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :table_name
|
13
|
+
|
14
|
+
def table
|
15
|
+
TwistlockControl.database.table(table_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def save(attributes)
|
19
|
+
with_connection do |conn|
|
20
|
+
table.get(attributes[:id]).replace(attributes).run(conn)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_by_id(id)
|
25
|
+
with_connection do |conn|
|
26
|
+
table.get(id).run(conn)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_by_attributes(attrs)
|
31
|
+
with_connection do |conn|
|
32
|
+
table.filter(attrs).limit(1).run(conn).first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_with_ids(ids)
|
37
|
+
with_connection do |conn|
|
38
|
+
table.get_all(*ids).run(conn)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove(id)
|
43
|
+
with_connection do |conn|
|
44
|
+
table.get(id).delete.run(conn)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def all
|
49
|
+
with_connection do |conn|
|
50
|
+
table.run(conn)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def with_connection
|
55
|
+
TwistlockControl.with_connection do |conn|
|
56
|
+
yield conn
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_table
|
61
|
+
with_connection do |conn|
|
62
|
+
TwistlockControl.database.table_create(table_name).run(conn)
|
63
|
+
end
|
64
|
+
rescue RethinkDB::RqlRuntimeError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def delete_all
|
69
|
+
with_connection do |conn|
|
70
|
+
table.delete.run(conn)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include TwistlockControl
|
4
|
+
|
5
|
+
describe Actions::Container do
|
6
|
+
attr_reader :container
|
7
|
+
|
8
|
+
describe 'adding a container' do
|
9
|
+
it 'should persist the container and synchronize its description' do
|
10
|
+
dir = Dir.pwd + '/../redis-container'
|
11
|
+
container = Actions::Container.add(name: 'redis', url: dir)
|
12
|
+
|
13
|
+
container = Entities::Container.find_by_id(container.id)
|
14
|
+
expect(container.url).to eq(dir)
|
15
|
+
expect(container.description).to be_a(ContainerDescription)
|
16
|
+
expect(container.description.name).to eq('redis')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include TwistlockControl
|
4
|
+
|
5
|
+
describe Actions::Provisioner do
|
6
|
+
describe '.add' do
|
7
|
+
it 'can be initialized from its attributes' do
|
8
|
+
prov = Actions::Provisioner.add(name: 'MyName', url: 'url')
|
9
|
+
expect(prov).to_not be_nil
|
10
|
+
expect(prov.name).to eq('MyName')
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'can be persisted and retrieved from the database' do
|
14
|
+
prov = Actions::Provisioner.add(name: 'MyName', url: 'url')
|
15
|
+
retrieved = Entities::Provisioner.find_by_id(prov.id)
|
16
|
+
expect(retrieved).to eq(prov)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '.update' do
|
21
|
+
it 'updates a provisioner and persists it' do
|
22
|
+
prov = Actions::Provisioner.add(name: 'MyName', url: 'url')
|
23
|
+
Actions::Provisioner.update(prov.id, name: 'MyNewName')
|
24
|
+
prov = Entities::Provisioner.find_by_id(prov.id)
|
25
|
+
expect(prov.name).to eq('MyNewName')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '.remove' do
|
30
|
+
it 'removes provisioners' do
|
31
|
+
prov = Actions::Provisioner.add(name: 'MyName', url: 'url')
|
32
|
+
prov.remove
|
33
|
+
prov = Entities::Provisioner.find_by_id(prov.id)
|
34
|
+
expect(prov).to be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include TwistlockControl
|
4
|
+
|
5
|
+
describe Actions::ServiceInstance do
|
6
|
+
def make_service
|
7
|
+
service = Entities::CompositeService.new(name: 'MyService')
|
8
|
+
@container = Entities::Container.new(name: 'MyContainer', url: 'someUrl')
|
9
|
+
@container.save
|
10
|
+
service.service_relations[@container.name] = @container.id
|
11
|
+
service.save
|
12
|
+
service
|
13
|
+
end
|
14
|
+
|
15
|
+
# The user creates a service instance to prepare for provisioning
|
16
|
+
# a service
|
17
|
+
describe 'Creating a service instance' do
|
18
|
+
def verify_service_instance(service, instance)
|
19
|
+
expect(instance.configuration).to be_a(Entities::CompositeConfiguration)
|
20
|
+
expect(instance.configuration.service_id).to eq(service.id)
|
21
|
+
verify_configurations(instance.configuration.configurations)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def verify_configurations(configurations)
|
26
|
+
expect(configurations.length).to eq(1)
|
27
|
+
expect(configurations[0]).to be_a(Entities::ContainerConfiguration)
|
28
|
+
expect(configurations[0].service_id).to eq(@container.id)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should create a service instance that has container descriptions
|
32
|
+
for each container of the service template' do
|
33
|
+
service = make_service
|
34
|
+
instance = Actions::ServiceInstance.add('my-instance', service)
|
35
|
+
expect(verify_service_instance(service, instance)).to be(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should be possible to retrieve a service instance from persistent storage' do
|
39
|
+
service = make_service
|
40
|
+
instance = Actions::ServiceInstance.add('my-instance', service)
|
41
|
+
|
42
|
+
instance = Entities::ServiceInstance.find_by_id(instance.id)
|
43
|
+
expect(instance).to_not be(nil)
|
44
|
+
expect(verify_service_instance(service, instance)).to be(true)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include TwistlockControl
|
4
|
+
|
5
|
+
describe Collections do
|
6
|
+
describe 'provisioners' do
|
7
|
+
it 'should be possible to listen for changes' do
|
8
|
+
expect(Collections.provisioners).to respond_to(:changes)
|
9
|
+
expect(Collections.services).to respond_to(:changes)
|
10
|
+
expect(Collections.service_instances).to respond_to(:changes)
|
11
|
+
expect(Collections.container_instances).to respond_to(:changes)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'entities/shared_service_specs'
|
3
|
+
|
4
|
+
include TwistlockControl::Entities
|
5
|
+
|
6
|
+
describe CompositeService do
|
7
|
+
it_should_behave_like 'a service'
|
8
|
+
|
9
|
+
it 'can be created and added to an service' do
|
10
|
+
service = CompositeService.new(name: 'MyService')
|
11
|
+
service.save
|
12
|
+
app = CompositeService.new(name: 'MyName')
|
13
|
+
app.service_relations[service.name] = service.id
|
14
|
+
app.save
|
15
|
+
expect(app.services).to include(service)
|
16
|
+
app = CompositeService.find_by_id(app.id)
|
17
|
+
expect(app.services).to include(service)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'can have containers added to it' do
|
21
|
+
container = Container.new(name: 'MyContainer', url: 'someUrl')
|
22
|
+
container.save
|
23
|
+
app = CompositeService.new(name: 'MyName')
|
24
|
+
app.service_relations[container.name] = container.id
|
25
|
+
app.save
|
26
|
+
app = CompositeService.find_by_id(app.id)
|
27
|
+
expect(app.services).to include(container)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can link two containers together' do
|
31
|
+
container = Container.new(name: 'redis', url: 'git@github.com:d-snp/redis-container.git')
|
32
|
+
container.save
|
33
|
+
container2 = Container.new(name: 'rails', url: 'git@github.com:d-snp/rails-container.git')
|
34
|
+
container2.save
|
35
|
+
app = CompositeService.new(name: 'WebApp')
|
36
|
+
app.save
|
37
|
+
app.service_relations[container.name] = container.id
|
38
|
+
app.service_relations[container2.name] = container2.id
|
39
|
+
|
40
|
+
app.links.push(ServiceLink.new(
|
41
|
+
provider_name: container.name,
|
42
|
+
provider_port_name: 'redis',
|
43
|
+
consumer_name: container2.name,
|
44
|
+
consumer_port_name: 'redis'
|
45
|
+
))
|
46
|
+
app.save
|
47
|
+
app = CompositeService.find_by_id(app.id)
|
48
|
+
|
49
|
+
link = app.links[0]
|
50
|
+
expect(link).to_not be_nil
|
51
|
+
expect(link.provider_name).to eq(container.name)
|
52
|
+
expect(link.consumer_name).to eq(container2.name)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'can find a bunch of services by ids' do
|
56
|
+
ids = []
|
57
|
+
(1..3).each do |i|
|
58
|
+
service = CompositeService.new(name: "Service#{i}")
|
59
|
+
ids << service.id
|
60
|
+
service.save
|
61
|
+
end
|
62
|
+
services = CompositeService.find_with_ids(ids)
|
63
|
+
expect(services.length).to be(3)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'can be initialized from its attributes' do
|
67
|
+
app = CompositeService.new(name: 'MyName')
|
68
|
+
expect(app.respond_to? :name).to be(true)
|
69
|
+
expect(app.name).to eq('MyName')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'can be persisted and retrieved from the database' do
|
73
|
+
app = CompositeService.new(name: 'MyName')
|
74
|
+
app.save
|
75
|
+
retrieved = CompositeService.find_by_id(app.id)
|
76
|
+
expect(retrieved).to eq(app)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'can get a list of services' do
|
80
|
+
(1..3).each do |i|
|
81
|
+
app = CompositeService.new(name: "App#{i}")
|
82
|
+
app.save
|
83
|
+
end
|
84
|
+
retrieved = CompositeService.all
|
85
|
+
expect(retrieved.length).to eq(3)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'can remove services' do
|
89
|
+
app = CompositeService.new(name: 'MyName')
|
90
|
+
app.save
|
91
|
+
app.remove
|
92
|
+
app = CompositeService.find_by_id(app.id)
|
93
|
+
expect(app).to be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should not somehow get confused with other tables' do
|
97
|
+
app = CompositeService.new(name: 'MyName')
|
98
|
+
app.save
|
99
|
+
prov = Provisioner.new(name: 'MyName', url: 'someUrl')
|
100
|
+
prov.save
|
101
|
+
expect(CompositeService.all.length).to be(1)
|
102
|
+
expect(Provisioner.all.length).to be(1)
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'instances' do
|
106
|
+
before :each do
|
107
|
+
@container = Container.new(name: 'redis', url: 'git@github.com:d-snp/redis-container.git')
|
108
|
+
@container.save
|
109
|
+
@container2 = Container.new(name: 'rails', url: 'git@github.com:d-snp/rails-container.git')
|
110
|
+
@container2.save
|
111
|
+
@app = CompositeService.new(name: 'WebApp')
|
112
|
+
@app.save
|
113
|
+
@app.service_relations[@container.name] = @container.name
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'can expose a port exposed by a service' do
|
117
|
+
# expose the redis provided service of the the redis container as 'redis'
|
118
|
+
@app.provided_services['redis'] = { 'redis' => 'redis' }
|
119
|
+
@app.save
|
120
|
+
app = CompositeService.find_by_id(@app.id)
|
121
|
+
exposed = app.provided_services['redis']
|
122
|
+
expect(exposed).to_not be_nil
|
123
|
+
expect(exposed).to eq('redis' => 'redis')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|