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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rubocop.yml +6 -0
  4. data/Gemfile +16 -0
  5. data/Guardfile +30 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +47 -0
  8. data/Rakefile +1 -0
  9. data/features/provisioning_service_instances.feature +14 -0
  10. data/features/step_definitions/provisioning_service_instances_steps.rb +33 -0
  11. data/features/support/env.rb +22 -0
  12. data/lib/twistlock_control.rb +65 -0
  13. data/lib/twistlock_control/actions.rb +7 -0
  14. data/lib/twistlock_control/actions/container.rb +42 -0
  15. data/lib/twistlock_control/actions/container_instance.rb +28 -0
  16. data/lib/twistlock_control/actions/provisioner.rb +25 -0
  17. data/lib/twistlock_control/actions/service.rb +18 -0
  18. data/lib/twistlock_control/actions/service_instance.rb +40 -0
  19. data/lib/twistlock_control/collections.rb +22 -0
  20. data/lib/twistlock_control/entities.rb +11 -0
  21. data/lib/twistlock_control/entities/composite_service.rb +86 -0
  22. data/lib/twistlock_control/entities/container.rb +66 -0
  23. data/lib/twistlock_control/entities/container_instance.rb +22 -0
  24. data/lib/twistlock_control/entities/provisioner.rb +24 -0
  25. data/lib/twistlock_control/entities/provisioning_configuration.rb +65 -0
  26. data/lib/twistlock_control/entities/service.rb +19 -0
  27. data/lib/twistlock_control/entities/service_instance.rb +122 -0
  28. data/lib/twistlock_control/entity.rb +63 -0
  29. data/lib/twistlock_control/provisioner_api.rb +43 -0
  30. data/lib/twistlock_control/rethinkdb_repository.rb +74 -0
  31. data/lib/twistlock_control/version.rb +4 -0
  32. data/spec/actions/container_spec.rb +19 -0
  33. data/spec/actions/provisioner_spec.rb +37 -0
  34. data/spec/actions/service_instance_spec.rb +47 -0
  35. data/spec/collections_spec.rb +14 -0
  36. data/spec/entities/composite_service_spec.rb +126 -0
  37. data/spec/entities/container_spec.rb +8 -0
  38. data/spec/entities/provisioner_spec.rb +56 -0
  39. data/spec/entities/service_instance_spec.rb +33 -0
  40. data/spec/entities/shared_service_specs.rb +4 -0
  41. data/spec/provisioner_api_spec.rb +35 -0
  42. data/spec/rethinkdb_repository_spec.rb +15 -0
  43. data/spec/spec_helper.rb +25 -0
  44. data/twistlock-control.gemspec +29 -0
  45. 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,4 @@
1
+ # Update this for releases
2
+ module TwistlockControl
3
+ VERSION = '0.0.1'
4
+ 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
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'entities/shared_service_specs'
3
+
4
+ include TwistlockControl
5
+
6
+ describe Entities::Container do
7
+ it_should_behave_like 'a service'
8
+ end