kumo_dockercloud 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aa4d5ceb6b52b11070c8c24ff454f9b5b9394548
4
- data.tar.gz: 5a0b1624adc85788d28a9ec56d958a6ecd743cbd
3
+ metadata.gz: f17aa8666415f9aca28a1bff56fd4f15008ac281
4
+ data.tar.gz: 69a0f07ebf17b9a59fdf426a7e567f620f862b88
5
5
  SHA512:
6
- metadata.gz: 3a171ca8c7bcba09b966c5043c6ac8a2a63c97e5f03b08dca279b7e293aa688e9e78dcf7ead95c29c8943646c8eaef0b9691648d1cb5cda76268572aafe5a477
7
- data.tar.gz: 1213275c4960c57efe4a7e301ff6d60324b3b6db9b15a18fb7a9a0dd09d474bd442bbddfde215f9f16455c320bba68e87218990ecf72fa9e5ee9408c1b3baecb
6
+ metadata.gz: c4c7cec1ea8cce00a0ac8cd85375ec7ab965c44f02fd9516f0be9e0cb4598e9fbab4bb1d4034f406c4e845f71b76f27ca20e0f371b5c39a7ef4a7b9daba90fed
7
+ data.tar.gz: a06f463691c216e733f47d83f16713ce85706f5befff163f97bc11d8d52689ecdd72a04271afb328446a7aa009a9870cf4186f3c632751f3641d4a597dbe986d
@@ -2,6 +2,8 @@ require 'timeout'
2
2
 
3
3
  module KumoDockerCloud
4
4
  class Service
5
+ attr_reader :name
6
+
5
7
  def initialize(stack_name, service_name)
6
8
  @stack_name = stack_name
7
9
  @name = service_name
@@ -35,11 +37,33 @@ module KumoDockerCloud
35
37
  raise KumoDockerCloud::ServiceDeployError.new("One or more checks failed to pass within the timeout")
36
38
  end
37
39
 
40
+ def links
41
+ get_service.linked_to_service.map { |service| KumoDockerCloud::Service.new(stack_name, service[:name]) }
42
+ end
43
+
44
+ def set_link(service_to_link)
45
+ linked_service = {
46
+ to_service: service_to_link.resource_uri,
47
+ name: service_to_link.name,
48
+ from_service: resource_uri
49
+ }
50
+
51
+ docker_cloud_api.services.update(uuid, linked_to_service: [linked_service])
52
+ end
53
+
54
+ def stop
55
+ docker_cloud_api.services.stop(uuid)
56
+ end
57
+
58
+ def resource_uri
59
+ get_service.resource_uri
60
+ end
61
+
38
62
  private
39
- attr_reader :stack_name, :name
63
+ attr_reader :stack_name
40
64
 
41
65
  def containers
42
- service_api.containers
66
+ get_service.containers
43
67
  end
44
68
 
45
69
  def update_image(version)
@@ -58,16 +82,16 @@ module KumoDockerCloud
58
82
  @docker_cloud_api ||= KumoDockerCloud::DockerCloudApi.new
59
83
  end
60
84
 
61
- def service_api
85
+ def get_service
62
86
  docker_cloud_api.service_by_stack_and_service_name(stack_name, name)
63
87
  end
64
88
 
65
89
  def uuid
66
- service_api.uuid
90
+ get_service.uuid
67
91
  end
68
92
 
69
93
  def image_name
70
- service_api.image_name.split(':').first
94
+ get_service.image_name.split(':').first
71
95
  end
72
96
  end
73
97
  end
@@ -2,6 +2,7 @@ module KumoDockerCloud
2
2
  class Stack
3
3
  attr_reader :stack_name, :app_name, :options
4
4
 
5
+ #TODO delete options
5
6
  def initialize(app_name, env_name, options = { contactable: true })
6
7
  @app_name = app_name
7
8
  @stack_name = "#{app_name}-#{env_name}"
@@ -17,6 +18,28 @@ module KumoDockerCloud
17
18
  service.check(checks, check_timeout) if checks
18
19
  end
19
20
 
21
+ def deploy_blue_green(options)
22
+ service_names = options[:service_names]
23
+ version = options[:version]
24
+ checks = options[:checks]
25
+ check_timeout = options[:check_timeout]
26
+ switching_service_name = options[:switching_service_name]
27
+
28
+ validate_params(version, "Version")
29
+ validate_params(service_names, "Service names")
30
+ validate_params(switching_service_name, "Switching service name")
31
+
32
+ services = service_names.map { |service_name| Service.new(stack_name, service_name) }
33
+
34
+ switching_service = Service.new(stack_name, switching_service_name)
35
+ green_service = switching_service.links.find { |linked_service| services.find { |service| service.name == linked_service.name } }
36
+ blue_service = services.find { |service| service.name != green_service.name }
37
+
38
+ deploy(blue_service.name, version, checks, check_timeout)
39
+ switching_service.set_link(blue_service)
40
+ green_service.stop
41
+ end
42
+
20
43
  private
21
44
 
22
45
  def validate_params(param_value, param_name)
@@ -5,14 +5,11 @@ module KumoDockerCloud
5
5
  def self.create_from_template(stack_template, config, env_vars)
6
6
  parsed = YAML.load(ERB.new(stack_template).result(config.get_binding))
7
7
 
8
- parsed[config.app_name]['environment'] ||= {}
9
- parsed[config.app_name]['environment'].merge!(config.plain_text_secrets)
10
- parsed[config.app_name]['environment'].merge!(env_vars.fetch(config.app_name, {}))
11
-
12
8
  converted_env_vars = make_all_root_level_keys_strings(env_vars)
13
9
 
14
10
  env_vars.each do |key, _|
15
11
  key_string = key.to_s
12
+ parsed[key_string]['environment'] ||= {}
16
13
  parsed[key_string]['environment'].merge!(converted_env_vars.fetch(key_string))
17
14
  end
18
15
 
@@ -1,3 +1,3 @@
1
1
  module KumoDockerCloud
2
- VERSION = '1.2.0'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -4,32 +4,33 @@ require 'httpi'
4
4
  describe KumoDockerCloud::Service do
5
5
  let(:service_image) { "repository/docker_image_name:version" }
6
6
  let(:service_uuid) { "i_am_a_unique_snowflower" }
7
- let(:docker_cloud_service_api) { double(:services_api, uuid: service_uuid, image_name: service_image, containers: []) }
8
- let(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_service_api)}
7
+ let(:docker_cloud_service) { double(:service, uuid: service_uuid, image_name: service_image, containers: [], resource_uri: "api/v1/#{service_uuid}")}
8
+ let(:docker_cloud_services_api) { double(:services_api) }
9
+ let(:docker_cloud_api) { instance_double(KumoDockerCloud::DockerCloudApi, services: docker_cloud_services_api)}
9
10
 
10
11
  subject { described_class.new('stack_name', 'service_name') }
11
12
 
12
13
  before do
13
14
  allow(KumoDockerCloud::DockerCloudApi).to receive(:new).and_return(docker_cloud_api)
14
15
 
15
- allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(docker_cloud_service_api)
16
+ allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(docker_cloud_service)
16
17
  end
17
18
 
18
19
  describe '#deploy' do
19
20
  it 'runs the actual update and redeploy methods' do
20
- expect(docker_cloud_service_api).to receive(:update).with(service_uuid, { image: service_image })
21
- expect(docker_cloud_service_api).to receive(:redeploy).with(service_uuid)
21
+ expect(docker_cloud_services_api).to receive(:update).with(service_uuid, { image: service_image })
22
+ expect(docker_cloud_services_api).to receive(:redeploy).with(service_uuid)
22
23
  subject.deploy('version')
23
24
  end
24
25
 
25
26
  it 'raises an appropriate exception when there is an error during image update' do
26
- expect(docker_cloud_service_api).to receive(:update).and_raise(RestClient::InternalServerError)
27
+ expect(docker_cloud_services_api).to receive(:update).and_raise(RestClient::InternalServerError)
27
28
  expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
28
29
  end
29
30
 
30
31
  it 'raises an appropriate exception when there is an error during redployment' do
31
- allow(docker_cloud_service_api).to receive(:update).with(service_uuid, { image: service_image })
32
- expect(docker_cloud_service_api).to receive(:redeploy).and_raise(RestClient::InternalServerError)
32
+ allow(docker_cloud_services_api).to receive(:update).with(service_uuid, { image: service_image })
33
+ expect(docker_cloud_services_api).to receive(:redeploy).and_raise(RestClient::InternalServerError)
33
34
  expect { subject.deploy('version') }.to raise_error(KumoDockerCloud::ServiceDeployError, "Something went wrong during service update on Docker Cloud's end")
34
35
  end
35
36
  end
@@ -54,7 +55,7 @@ describe KumoDockerCloud::Service do
54
55
  allow(http_lib).to receive(:get).with("http://whale1.test/site_status").and_return(200)
55
56
  allow(http_lib).to receive(:get).with("http://whale2.test/site_status").and_return("timeout", "timeout", 200)
56
57
  allow(whale1).to receive(:state).and_return("Starting", "Running")
57
- allow(docker_cloud_service_api).to receive(:containers).and_return(containers)
58
+ allow(docker_cloud_service).to receive(:containers).and_return(containers)
58
59
  end
59
60
 
60
61
  it 'resolves to true if all the checks eventually pass' do
@@ -68,5 +69,66 @@ describe KumoDockerCloud::Service do
68
69
  allow(whale1).to receive(:state).and_return("Starting")
69
70
  expect { subject.check(checks, short_timeout) }.to raise_error(KumoDockerCloud::ServiceDeployError, "One or more checks failed to pass within the timeout")
70
71
  end
72
+
73
+ describe "#links" do
74
+ let(:linked_service_uuid) { "i_am_the_db" }
75
+ let(:linked_service_name) { "db" }
76
+ let(:linked_to_service) do
77
+ {
78
+ from_service: service_uuid,
79
+ name: linked_service_name,
80
+ to_service: linked_service_uuid
81
+ }
82
+ end
83
+
84
+ before do
85
+ allow(docker_cloud_service).to receive(:linked_to_service).and_return([linked_to_service])
86
+ end
87
+
88
+ it "returns a list of KumoDockerCloud::Service object that are linked to from this service" do
89
+ links = subject.links
90
+ expect(links.first).to have_attributes(name: linked_service_name)
91
+ expect(links.size).to eq(1)
92
+ end
93
+
94
+ it "returns an empty array if there are no links" do
95
+ allow(docker_cloud_service).to receive(:linked_to_service).and_return([])
96
+ expect(subject.links).to eq([])
97
+ end
98
+ end
99
+
100
+ describe "#set_link" do
101
+ let(:linked_service_uuid) { "i_am_the_db" }
102
+ let(:linked_service_name) { "db" }
103
+ let(:linked_to_service) do
104
+ {
105
+ to_service: "api/v1/#{linked_service_uuid}",
106
+ name: linked_service_name,
107
+ from_service: "api/v1/#{service_uuid}"
108
+ }
109
+ end
110
+ let(:linked_service) { KumoDockerCloud::Service.new('stack_name', linked_service_name) }
111
+ let(:this_service) { double(:this_service, uuid: service_uuid, resource_uri: "api/v1/#{service_uuid}") }
112
+ let(:linked_service) { double(:linked_service, uuid: linked_service_uuid, resource_uri: "api/v1/#{linked_service_uuid}", name: linked_service_name) }
113
+
114
+ before do
115
+ allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', 'service_name').and_return(this_service)
116
+ allow(docker_cloud_api).to receive(:service_by_stack_and_service_name).with('stack_name', linked_service_name).and_return(linked_service)
117
+ end
118
+
119
+ it "updates the link attribute" do
120
+ expect(docker_cloud_services_api).to receive(:update).with(service_uuid, { linked_to_service: [linked_to_service] })
121
+
122
+ subject.set_link(linked_service)
123
+ end
124
+ end
125
+
126
+ describe "#stop" do
127
+ it "sends a request to stop the service" do
128
+ expect(docker_cloud_services_api).to receive(:stop).with(service_uuid)
129
+
130
+ subject.stop
131
+ end
132
+ end
71
133
  end
72
134
  end
@@ -32,8 +32,6 @@ describe KumoDockerCloud::StackFile do
32
32
  expect(subject).to eq(app_name => {
33
33
  'image' => 'a-thing',
34
34
  'environment' => {
35
- 'TEST_ENV' => 'FAKE',
36
- 'MORE' => 'ANOTHER',
37
35
  'KEY' => 'VALUE'
38
36
  }
39
37
  })
@@ -53,8 +51,6 @@ describe KumoDockerCloud::StackFile do
53
51
  'image' => 'a-thing',
54
52
  'environment' => {
55
53
  'TEST' => 'thing',
56
- 'TEST_ENV' => 'FAKE',
57
- 'MORE' => 'ANOTHER',
58
54
  'KEY' => 'VALUE'
59
55
  }
60
56
  })
@@ -72,37 +68,12 @@ describe KumoDockerCloud::StackFile do
72
68
  expect(subject).to eq(app_name => {
73
69
  'image' => 'a-thing',
74
70
  'environment' => {
75
- 'TEST_ENV' => 'FAKE',
76
- 'MORE' => 'ANOTHER',
77
71
  'KEY' => 'VALUE'
78
72
  }
79
73
  })
80
74
  end
81
75
  end
82
76
 
83
- context 'no app name env var' do
84
- let(:env_vars) do
85
- { }
86
- end
87
-
88
- let(:stack_template) do
89
- <<-eos
90
- application-stack-name:
91
- image: a-thing
92
- eos
93
- end
94
-
95
- it 'should create the environment with secrets in it' do
96
- expect(subject).to eq(app_name => {
97
- 'image' => 'a-thing',
98
- 'environment' => {
99
- 'TEST_ENV' => 'FAKE',
100
- 'MORE' => 'ANOTHER'
101
- }
102
- })
103
- end
104
- end
105
-
106
77
  context 'with other services' do
107
78
  let(:env_vars) do
108
79
  {
@@ -130,8 +101,6 @@ describe KumoDockerCloud::StackFile do
130
101
  app_name => {
131
102
  'image' => 'a-thing',
132
103
  'environment' => {
133
- 'TEST_ENV' => 'FAKE',
134
- 'MORE' => 'ANOTHER',
135
104
  'KEY' => 'VALUE'
136
105
  }},
137
106
  'another_service' => {
@@ -60,4 +60,83 @@ describe KumoDockerCloud::Stack do
60
60
  end
61
61
 
62
62
  end
63
+
64
+ describe '#deploy_blue_green' do
65
+ let(:service_a_uuid) { 'service_a_uuid' }
66
+ let(:service_a) { instance_double(KumoDockerCloud::Service, :service_a, uuid: uuid, name: "service-a") }
67
+ let(:service_b_uuid) { 'service_b_uuid' }
68
+ let(:service_b) { instance_double(KumoDockerCloud::Service, :service_b, uuid: uuid, name: "service-b") }
69
+ let(:nginx) { instance_double(KumoDockerCloud::Service, uuid: "nginx_uuid") }
70
+ let(:version) { "1" }
71
+ let(:deployment_checks) { [] }
72
+ let(:links) { [service_a] }
73
+ let(:check_timeout) { 120 }
74
+ let(:deploy_options) do
75
+ {
76
+ service_names: ["service-a", "service-b"],
77
+ version: version,
78
+ checks: deployment_checks,
79
+ check_timeout: check_timeout,
80
+ switching_service_name: "nginx"
81
+ }
82
+ end
83
+
84
+ subject { described_class.new(app_name, environment_name).deploy_blue_green(deploy_options) }
85
+
86
+ before do
87
+ allow(KumoDockerCloud::Service).to receive(:new)
88
+ .with(stack_name, "service-a")
89
+ .and_return(service_a)
90
+
91
+ allow(KumoDockerCloud::Service).to receive(:new)
92
+ .with(stack_name, "service-b")
93
+ .and_return(service_b)
94
+
95
+ allow(KumoDockerCloud::Service).to receive(:new)
96
+ .with(stack_name, "nginx")
97
+ .and_return(nginx)
98
+
99
+ allow(nginx).to receive(:links).and_return(links)
100
+ allow(nginx).to receive(:set_link).with(service_b)
101
+
102
+ allow(service_a).to receive(:stop)
103
+
104
+ allow(service_b).to receive(:deploy).with(version)
105
+ allow(service_b).to receive(:check).with(deployment_checks, check_timeout)
106
+ end
107
+
108
+ context 'when parameters are missing' do
109
+ it 'blow up when version is missing' do
110
+ deploy_options.delete(:version)
111
+ expect{ subject }.to raise_error(KumoDockerCloud::Error, "Version cannot be nil")
112
+ end
113
+
114
+ it 'blow up when service_names are missing' do
115
+ deploy_options.delete(:service_names)
116
+ expect{ subject }.to raise_error(KumoDockerCloud::Error, "Service names cannot be nil")
117
+ end
118
+
119
+ it 'blow up when switching_service_name is missing' do
120
+ deploy_options.delete(:switching_service_name)
121
+ expect{ subject }.to raise_error(KumoDockerCloud::Error, "Switching service name cannot be nil")
122
+ end
123
+ end
124
+
125
+ it 'deploys to the blue service only' do
126
+ expect(service_b).to receive(:deploy).with(version)
127
+ expect(service_b).to receive(:check).with(deployment_checks, check_timeout)
128
+ expect(service_a).to_not receive(:deploy)
129
+ subject
130
+ end
131
+
132
+ it 'switches over to the blue service on a successful deployment' do
133
+ expect(nginx).to receive(:set_link).with(service_b)
134
+ subject
135
+ end
136
+
137
+ it 'shuts down the previously green service' do
138
+ expect(service_a).to receive(:stop)
139
+ subject
140
+ end
141
+ end
63
142
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumo_dockercloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Redbubble
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-04-19 00:00:00.000000000 Z
13
+ date: 2016-04-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: httpi