avst-cloud 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +36 -0
- data/.travis.yml +16 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +191 -0
- data/README.md +201 -0
- data/Rakefile +4 -0
- data/avst-cloud.gemspec +28 -0
- data/bin/avst-cloud +99 -0
- data/bin/avst-cloud-puppet +173 -0
- data/bin/avst-cloud-rackspace +87 -0
- data/config/custom_system_config/hiera-configs/defaults/example.yaml +1 -0
- data/config/custom_system_config/hiera-configs/hostname1.yaml +12 -0
- data/config/custom_system_config/hiera-configs/hostname1_facts.yaml +53 -0
- data/config/custom_system_config/hiera-configs/templates/example.yaml +137 -0
- data/config/deploy.rb +79 -0
- data/config/deploy/production.rb +48 -0
- data/lib/avst-cloud.rb +28 -0
- data/lib/avst-cloud/aws_connection.rb +203 -0
- data/lib/avst-cloud/aws_server.rb +56 -0
- data/lib/avst-cloud/cloud_connection.rb +39 -0
- data/lib/avst-cloud/cloud_server.rb +89 -0
- data/lib/avst-cloud/logging.rb +36 -0
- data/lib/avst-cloud/rackspace_connection.rb +141 -0
- data/lib/avst-cloud/rackspace_server.rb +58 -0
- data/lib/avst-cloud/task.rb +288 -0
- data/lib/capistrano/tasks/prepare_puppet_and_execute.rake +90 -0
- metadata +188 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './cloud_server.rb'
|
16
|
+
|
17
|
+
module AvstCloud
|
18
|
+
class AwsServer < AvstCloud::CloudServer
|
19
|
+
|
20
|
+
def stop
|
21
|
+
if @server
|
22
|
+
logger.debug "Stopping #{@server_name}"
|
23
|
+
@server.stop
|
24
|
+
wait_for_state() {|serv| serv.state == 'stopped'}
|
25
|
+
logger.debug "[DONE]\n\n"
|
26
|
+
logger.debug "Server #{@server_name} stopped...".green
|
27
|
+
else
|
28
|
+
raise "Server #{@server_name} does not exist!".red
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
if @server
|
34
|
+
logger.debug "Starting #{@server_name}"
|
35
|
+
@server.start
|
36
|
+
wait_for_state() {|serv| serv.ready?}
|
37
|
+
logger.debug "[DONE]\n\n"
|
38
|
+
logger.debug "Server #{@server_name} started...".green
|
39
|
+
else
|
40
|
+
raise "Server #{@server_name} does not exist!".red
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def destroy
|
45
|
+
if @server
|
46
|
+
logger.debug "Killing #{@server_name}"
|
47
|
+
@server.destroy
|
48
|
+
wait_for_state() {|serv| serv.state == 'terminated'}
|
49
|
+
logger.debug "Server #{@server_name} destroyed...".green
|
50
|
+
else
|
51
|
+
raise "Server #{@server_name} does not exist!".red
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './logging.rb'
|
16
|
+
|
17
|
+
module AvstCloud
|
18
|
+
class CloudConnection
|
19
|
+
include Logging
|
20
|
+
attr_accessor :connection, :provider, :provider_user, :provider_pass
|
21
|
+
|
22
|
+
def initialize(provider, provider_user, provider_pass)
|
23
|
+
@provider = provider
|
24
|
+
@provider_access_user = provider_user
|
25
|
+
@provider_access_pass = provider_pass
|
26
|
+
end
|
27
|
+
|
28
|
+
# Abstract classes to be implemented per provider
|
29
|
+
UNIMPLEMENTED="Unimplemented..."
|
30
|
+
|
31
|
+
def server(server_name, root_user, root_password, os=nil)
|
32
|
+
raise UNIMPLEMENTED
|
33
|
+
end
|
34
|
+
|
35
|
+
def list_known_servers
|
36
|
+
raise UNIMPLEMENTED
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './logging.rb'
|
16
|
+
require_relative './task.rb'
|
17
|
+
|
18
|
+
module AvstCloud
|
19
|
+
class CloudServer
|
20
|
+
include Logging
|
21
|
+
attr_accessor :server, :server_name, :ip_address, :access_user, :access_password
|
22
|
+
|
23
|
+
def initialize(server, server_name, ip_address, access_user, access_password)
|
24
|
+
@server = server
|
25
|
+
@ip_address = ip_address
|
26
|
+
@server_name = server_name
|
27
|
+
@access_user = access_user
|
28
|
+
@access_password = access_password
|
29
|
+
end
|
30
|
+
|
31
|
+
def bootstrap(pre_upload_commands, custom_file_uploads, post_upload_commands, remote_server_debug, debug_structured_log)
|
32
|
+
logger.debug "Bootstrapping #{server_name}...".green
|
33
|
+
run_tasks([AvstCloud::WaitUntilReady.new])
|
34
|
+
disable_tty_task = AvstCloud::DisableRequireTty.new(@access_user)
|
35
|
+
pre_upload_commands_tasks = AvstCloud::SshCommandTask.new(pre_upload_commands, remote_server_debug, debug_structured_log)
|
36
|
+
custom_file_uploads_tasks = AvstCloud::ScpTask.new(custom_file_uploads)
|
37
|
+
post_upload_commands_tasks = AvstCloud::SshCommandTask.new(post_upload_commands, remote_server_debug, debug_structured_log)
|
38
|
+
run_tasks([disable_tty_task, pre_upload_commands_tasks, custom_file_uploads_tasks, post_upload_commands_tasks])
|
39
|
+
logger.debug "Bootstrap done. You can connect to server as #{@access_user} on #{@ip_address}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def provision(git, branch, server_tmp_folder, reference, custom_provisioning_commands, puppet_runner, puppet_runner_prepare, destination_folder)
|
43
|
+
logger.debug "Provisioning #{@server_name}..."
|
44
|
+
provision_task = AvstCloud::CapistranoDeploymentTask.new(git, branch, server_tmp_folder, reference, custom_provisioning_commands, puppet_runner, puppet_runner_prepare, destination_folder)
|
45
|
+
run_tasks([AvstCloud::WaitUntilReady.new, provision_task])
|
46
|
+
logger.debug "Provisioning done. You can connect to server on #{@ip_address}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def post_provisioning_cleanup(custom_commands, os, remote_server_debug, server_tmp_folder)
|
50
|
+
logger.debug "Cleaning up after provisioning #{server_name}..."
|
51
|
+
custom_cleanup_commands = AvstCloud::SshCommandTask.new(custom_commands, remote_server_debug, true)
|
52
|
+
run_tasks([AvstCloud::WaitUntilReady.new, AvstCloud::PostProvisionCleanup.new(os, remote_server_debug, server_tmp_folder), custom_cleanup_commands])
|
53
|
+
logger.debug "Post provisioning cleanup done. You can connect to server as #{@access_user} on #{@ip_address}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_tasks(tasks)
|
57
|
+
Array(tasks).each do |task|
|
58
|
+
task.execute self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def status
|
63
|
+
@server.state
|
64
|
+
end
|
65
|
+
|
66
|
+
def wait_for_state(&cond)
|
67
|
+
logger.debug "Waiting for state change...".yellow
|
68
|
+
@server.wait_for(600, 5) do
|
69
|
+
print "."
|
70
|
+
STDOUT.flush
|
71
|
+
cond.call(self)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Abstract classes to be implemented per provider
|
76
|
+
UNIMPLEMENTED="Unimplemented..."
|
77
|
+
def stop
|
78
|
+
raise UNIMPLEMENTED
|
79
|
+
end
|
80
|
+
|
81
|
+
def start
|
82
|
+
raise UNIMPLEMENTED
|
83
|
+
end
|
84
|
+
|
85
|
+
def destroy
|
86
|
+
raise UNIMPLEMENTED
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'logger'
|
16
|
+
|
17
|
+
module Logging
|
18
|
+
def self.included(base)
|
19
|
+
class << base
|
20
|
+
def logger
|
21
|
+
@logger ||= Logger.new($stdout)
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger=(logger)
|
25
|
+
@logger = logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def logger
|
31
|
+
self.class.logger
|
32
|
+
end
|
33
|
+
def logger=(logger)
|
34
|
+
@logger = logger
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './cloud_connection.rb'
|
16
|
+
|
17
|
+
module AvstCloud
|
18
|
+
|
19
|
+
class RackspaceConnection < AvstCloud::CloudConnection
|
20
|
+
|
21
|
+
attr_accessor :region
|
22
|
+
|
23
|
+
def initialize(provider_access_user, provider_access_pass, region=:lon)
|
24
|
+
super('rackspace',provider_access_user, provider_access_pass)
|
25
|
+
@region = region
|
26
|
+
end
|
27
|
+
|
28
|
+
def server(server_name, root_user, root_password, os=nil)
|
29
|
+
server = find_fog_server(server_name)
|
30
|
+
if !root_user
|
31
|
+
root_user = "root"
|
32
|
+
end
|
33
|
+
AvstCloud::RackspaceServer.new(server, server_name, server.public_ip_address, root_user, root_password)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_server(server_name, image_id, flavor_id='4')
|
37
|
+
server_number, os="ubuntu14"
|
38
|
+
|
39
|
+
logger.debug "Creating Rackspace server:"
|
40
|
+
logger.debug "server_name - #{server_name}"
|
41
|
+
logger.debug "flavor_id - #{flavor_id}"
|
42
|
+
logger.debug "image_id - #{image_id}"
|
43
|
+
|
44
|
+
unless server_name and image_id
|
45
|
+
raise "Please provide server_name, image_id and flavor_id"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check existing server
|
49
|
+
existing_server = find_fog_server(server_name, false)
|
50
|
+
if existing_server && existing_server.state != 'SHUTOFF'
|
51
|
+
logger.debug "Server found in state: #{existing_server.state}"
|
52
|
+
raise "Server with the same name found!"
|
53
|
+
elsif existing_server && existing_server.state == 'SHUTOFF'
|
54
|
+
logger.debug "Server found and is stopped, restarting it."
|
55
|
+
existing_server.reboot 'HARD'
|
56
|
+
result_server = AvstCloud::RackspaceServer.new(existing_server, server_name, nil, nil , nil)
|
57
|
+
result_server.wait_for_state() {|serv| serv.ready?}
|
58
|
+
logger.debug "[DONE]\n\n"
|
59
|
+
logger.debug "The server was successfully re-started.\n\n"
|
60
|
+
else
|
61
|
+
# create server
|
62
|
+
server = connect.servers.create :name => server_name,
|
63
|
+
:flavor_id => flavor_id,
|
64
|
+
:image_id => image_id
|
65
|
+
begin
|
66
|
+
result_server = AvstCloud::RackspaceServer.new(server, server_name, nil, nil , nil)
|
67
|
+
# Check every 5 seconds to see if server is in the active state (ready?).
|
68
|
+
# If the server has not been built in 5 minutes (600 seconds) an exception will be raised.
|
69
|
+
result_server.wait_for_state() {|serv| serv.ready?}
|
70
|
+
logger.debug "[DONE]\n\n"
|
71
|
+
|
72
|
+
logger.debug "The server has been successfully created, to login onto the server:\n\n"
|
73
|
+
logger.debug "\t ssh #{server.username}@#{server.public_ip_address}\n\n"
|
74
|
+
|
75
|
+
rescue Fog::Errors::TimeoutError
|
76
|
+
logger.debug "[TIMEOUT]\n\n"
|
77
|
+
logger.debug "This server is currently #{server.progress}% into the build process and is taking longer to complete than expected."
|
78
|
+
logger.debug "You can continute to monitor the build process through the web console at https://mycloud.rackspace.com/\n\n"
|
79
|
+
raise "Timeout while creating Rackspace server #{server_name}"
|
80
|
+
end
|
81
|
+
logger.debug "The #{server.username} password is #{server.password}\n\n"
|
82
|
+
end
|
83
|
+
result_server.access_user = server.username
|
84
|
+
result_server.access_password = server.password
|
85
|
+
result_server.ip_address = server.public_ip_address
|
86
|
+
result_server
|
87
|
+
end
|
88
|
+
|
89
|
+
def server_status(server_name)
|
90
|
+
server = find_fog_server(server_name, false)
|
91
|
+
if server
|
92
|
+
server.state
|
93
|
+
else
|
94
|
+
'not_created'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def list_flavours
|
99
|
+
connect.flavors.each do |fl|
|
100
|
+
logger.debug fl.inspect
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def list_images
|
105
|
+
connect.images.each do |im|
|
106
|
+
logger.debug im.inspect
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns list of servers from fog
|
111
|
+
def list_known_servers
|
112
|
+
connect.servers.all
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_fog_server(server_name, should_fail=true)
|
116
|
+
serv = connect.servers.find{|serv| serv.name == server_name}
|
117
|
+
unless serv
|
118
|
+
if should_fail
|
119
|
+
logger.debug "Server not found for name: #{server_name}"
|
120
|
+
raise "Server not found for name: #{server_name}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
serv
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def connect
|
128
|
+
unless @connection
|
129
|
+
logger.debug "Creating new connection to rackspace: #{@provider_user} #{@provider_pass} #{@region}"
|
130
|
+
@connection = Fog::Compute.new({
|
131
|
+
:provider => 'rackspace',
|
132
|
+
:rackspace_username => @provider_access_user,
|
133
|
+
:rackspace_api_key => @provider_access_pass,
|
134
|
+
:version => :v2, # Use Next Gen Cloud Servers
|
135
|
+
:rackspace_region => @region
|
136
|
+
})
|
137
|
+
end
|
138
|
+
@connection
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './cloud_server.rb'
|
16
|
+
|
17
|
+
module AvstCloud
|
18
|
+
class RackspaceServer < AvstCloud::CloudServer
|
19
|
+
|
20
|
+
def stop
|
21
|
+
if @server
|
22
|
+
logger.debug "Stopping #{@server_name}"
|
23
|
+
|
24
|
+
shutdown_command = AvstCloud::SshCommandTask.new(['shutdown -h now'])
|
25
|
+
run_tasks([AvstCloud::WaitUntilReady.new, shutdown_command])
|
26
|
+
logger.debug "Waiting for SHUTOFF state..."
|
27
|
+
wait_for_state() {|serv| serv.state == 'SHUTOFF'}
|
28
|
+
logger.debug "[DONE]\n\n"
|
29
|
+
logger.debug "Server #{@server_name} stopped...".green
|
30
|
+
else
|
31
|
+
raise "Server #{@server_name} does not exist!".red
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def start
|
36
|
+
if @server
|
37
|
+
logger.debug "Starting #{@server_name}"
|
38
|
+
@server.reboot 'HARD'
|
39
|
+
logger.debug "Waiting for ACTIVE state..."
|
40
|
+
wait_for_state() {|serv| serv.ready?}
|
41
|
+
logger.debug "[DONE]\n\n"
|
42
|
+
logger.debug "Server #{@server_name} started...".green
|
43
|
+
else
|
44
|
+
raise "Server #{@server_name} does not exist!".red
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
if @server
|
50
|
+
logger.debug "Killing #{@server_name}"
|
51
|
+
@server.destroy
|
52
|
+
logger.debug "Server #{@server_name} destroyed...".green
|
53
|
+
else
|
54
|
+
raise "Server #{@server_name} does not exist!".red
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,288 @@
|
|
1
|
+
# Copyright 2015 Adaptavist.com Ltd.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require_relative './logging.rb'
|
16
|
+
|
17
|
+
module AvstCloud
|
18
|
+
class Task
|
19
|
+
include Logging
|
20
|
+
def initialize(debug = false)
|
21
|
+
@debug = debug
|
22
|
+
end
|
23
|
+
def execute(server)
|
24
|
+
raise 'Unimplemented...'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SshTask < AvstCloud::Task
|
29
|
+
include Logging
|
30
|
+
def execute(server)
|
31
|
+
unless server.ip_address
|
32
|
+
logger.error 'Can not find host'.red
|
33
|
+
raise 'Can not find ip address, access_user or access_password'
|
34
|
+
end
|
35
|
+
|
36
|
+
unless server.access_user
|
37
|
+
logger.error 'Access user not found. Please provide username for this server.'.red
|
38
|
+
raise 'Access user not found. Please provide username for this server.'
|
39
|
+
end
|
40
|
+
|
41
|
+
unless server.access_password
|
42
|
+
logger.error 'Password not found. Please provide password or pem key for this server.'.red
|
43
|
+
raise 'Password not found. Please provide root_password in config. for this server.'
|
44
|
+
end
|
45
|
+
|
46
|
+
logger.debug "Using #{server.access_user}@#{server.ip_address} with #{server.access_password} to perform ssh task."
|
47
|
+
attempts = 1
|
48
|
+
success = false
|
49
|
+
max_attempts = 50
|
50
|
+
while attempts < max_attempts and !success
|
51
|
+
begin
|
52
|
+
Net::SSH.start(server.ip_address, server.access_user, :password => server.access_password, :keys => [server.access_password]) do |session|
|
53
|
+
ssh_command session
|
54
|
+
end
|
55
|
+
success = true
|
56
|
+
rescue Errno::ECONNREFUSED
|
57
|
+
logger.debug "Connection refused. Server may not have booted yet. Sleeping #{attempts}/#{max_attempts}"
|
58
|
+
sleep(10)
|
59
|
+
attempts=attempts+1
|
60
|
+
rescue Errno::ETIMEDOUT
|
61
|
+
logger.debug "Connection timed out. Server may not have booted yet. Sleeping #{attempts}/#{max_attempts}"
|
62
|
+
sleep(10)
|
63
|
+
attempts=attempts+1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
unless success
|
67
|
+
logger.error 'Bootstrapping: failed to find server to connect to'
|
68
|
+
raise 'Bootstrapping: failed to find server to connect to'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def ssh_command(session)
|
73
|
+
raise 'Unimplemented...'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class SshCommandTask < AvstCloud::SshTask
|
78
|
+
include Logging
|
79
|
+
|
80
|
+
def initialize(cmds, debug = false, structured_log = false)
|
81
|
+
@cmds = cmds
|
82
|
+
@debug = debug
|
83
|
+
@structured_log = structured_log
|
84
|
+
end
|
85
|
+
|
86
|
+
def ssh_command(session)
|
87
|
+
Array(@cmds).each do |cmd|
|
88
|
+
next unless cmd
|
89
|
+
cmd.strip!
|
90
|
+
next if cmd == ""
|
91
|
+
logger.debug("Running command on server as root: sudo su -c \"#{cmd}\"")
|
92
|
+
start_time = Time.now
|
93
|
+
|
94
|
+
session.exec!("sudo su -c \"#{cmd}\"") do |ch, stream, data|
|
95
|
+
if @debug
|
96
|
+
logger.debug "Got this on the #{stream} stream: "
|
97
|
+
if @structured_log && logger.methods.include?(:log_structured_code)
|
98
|
+
logger.log_structured_code(data)
|
99
|
+
else
|
100
|
+
logger.debug(data)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
total_time = Time.now - start_time
|
105
|
+
logger.debug("Completed in #{total_time} seconds")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# In case Requiretty is set in sudoers disable it for bootstrapping and provisioning
|
111
|
+
# for user that performs it
|
112
|
+
class DisableRequireTty < AvstCloud::SshTask
|
113
|
+
include Logging
|
114
|
+
def initialize(for_user)
|
115
|
+
@for_user = for_user
|
116
|
+
end
|
117
|
+
def ssh_command(session)
|
118
|
+
session.open_channel do |channel|
|
119
|
+
channel.request_pty do |ch, success|
|
120
|
+
raise 'Error requesting pty' unless success
|
121
|
+
|
122
|
+
ch.send_channel_request('shell') do |ch, success|
|
123
|
+
raise 'Error opening shell' unless success
|
124
|
+
end
|
125
|
+
end
|
126
|
+
channel.on_data do |ch, data|
|
127
|
+
if @debug
|
128
|
+
STDOUT.print data
|
129
|
+
end
|
130
|
+
end
|
131
|
+
channel.on_extended_data do |ch, type, data|
|
132
|
+
STDOUT.print "Error: #{data}\n"
|
133
|
+
end
|
134
|
+
channel.send_data("sudo su -c 'echo 'Defaults:#{@for_user}\\ \\!requiretty' >> /etc/sudoers'\n")
|
135
|
+
channel.send_data("exit\n")
|
136
|
+
session.loop
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class WaitUntilReady < AvstCloud::SshTask
|
142
|
+
include Logging
|
143
|
+
|
144
|
+
def ssh_command(session)
|
145
|
+
session.open_channel do |channel|
|
146
|
+
channel.request_pty do |ch, success|
|
147
|
+
raise 'Error requesting pty' unless success
|
148
|
+
|
149
|
+
ch.send_channel_request("shell") do |ch, success|
|
150
|
+
raise 'Error opening shell' unless success
|
151
|
+
end
|
152
|
+
end
|
153
|
+
channel.on_data do |ch, data|
|
154
|
+
if @debug
|
155
|
+
STDOUT.print data
|
156
|
+
end
|
157
|
+
end
|
158
|
+
channel.on_extended_data do |ch, type, data|
|
159
|
+
STDOUT.print "Error: #{data}\n"
|
160
|
+
end
|
161
|
+
channel.send_data("echo \"ready\"\n")
|
162
|
+
channel.send_data("exit\n")
|
163
|
+
session.loop
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class CapistranoDeploymentTask < AvstCloud::Task
|
169
|
+
include Logging
|
170
|
+
|
171
|
+
def initialize(git, branch, server_tmp_folder = "/tmp/avst_cloud_tmp_#{Time.now.to_i}", reference = nil, custom_provisioning_commands = [], puppet_runner = nil, puppet_runner_prepare = nil, destination_folder = '/var/opt/puppet')
|
172
|
+
unless git and (branch or reference)
|
173
|
+
logger.debug "You have to provide git repo url #{git} and git branch #{branch} or git tag reference #{reference}".red
|
174
|
+
raise "You have to provide git repo url #{git} and git branch #{branch} or git tag reference #{reference}"
|
175
|
+
end
|
176
|
+
|
177
|
+
@git = git
|
178
|
+
@branch = branch
|
179
|
+
@server_tmp_folder = server_tmp_folder
|
180
|
+
@reference = reference
|
181
|
+
@custom_provisioning_commands = custom_provisioning_commands || []
|
182
|
+
@puppet_runner = puppet_runner
|
183
|
+
@puppet_runner_prepare = puppet_runner_prepare
|
184
|
+
@destination_folder = destination_folder || '/var/opt/puppet'
|
185
|
+
end
|
186
|
+
|
187
|
+
def execute(server)
|
188
|
+
unless server.ip_address
|
189
|
+
logger.error 'Can not find host'.red
|
190
|
+
raise 'Can not find ip address, access_user or access_password'
|
191
|
+
end
|
192
|
+
|
193
|
+
unless server.access_user
|
194
|
+
logger.error 'Access user not found. Please provide username for this server.'.red
|
195
|
+
raise 'Access user not found. Please provide username for this server.'
|
196
|
+
end
|
197
|
+
|
198
|
+
unless server.access_password
|
199
|
+
logger.error 'Password not found. Please provide password or pem key for this server.'.red
|
200
|
+
raise 'Password not found. Please provide root_password in config. for this server.'
|
201
|
+
end
|
202
|
+
|
203
|
+
logger.debug "Using #{server.access_user}@#{server.ip_address} with #{server.access_password}"
|
204
|
+
|
205
|
+
|
206
|
+
# Initiate capistrano deploy script to download the laters code and provision the server
|
207
|
+
require 'capistrano/all'
|
208
|
+
require 'capistrano/setup'
|
209
|
+
require 'capistrano/deploy'
|
210
|
+
Dir.glob("#{File.dirname __dir__}/capistrano/tasks/*.rake").each { |r| load r }
|
211
|
+
# cap production deploy
|
212
|
+
ENV['cap_git_repo_url'] = @git
|
213
|
+
ENV['cap_branch_name'] = @branch
|
214
|
+
ENV['cap_reference_name'] = @reference
|
215
|
+
ENV['cap_ip_address'] = server.ip_address
|
216
|
+
ENV['cap_access_password'] = server.access_password
|
217
|
+
ENV['cap_access_user'] = server.access_user
|
218
|
+
ENV['server_name'] = server.server_name
|
219
|
+
ENV['puppet_runner'] = @puppet_runner
|
220
|
+
ENV['puppet_runner_prepare'] = @puppet_runner_prepare
|
221
|
+
ENV['avst_cloud_tmp_folder'] = @server_tmp_folder
|
222
|
+
ENV['custom_provisioning_commands'] = @custom_provisioning_commands.to_json
|
223
|
+
ENV['destination_folder'] = @destination_folder
|
224
|
+
logger.debug "Using git #{@git} branch #{@branch} to provision #{server.ip_address}"
|
225
|
+
|
226
|
+
Capistrano::Application.invoke('production')
|
227
|
+
Capistrano::Application.invoke('deploy')
|
228
|
+
logger.debug "You can connect to server on #{server.ip_address}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class PostProvisionCleanup < AvstCloud::SshCommandTask
|
233
|
+
|
234
|
+
def initialize(os, debug, tmp_folder = "/tmp/avst_cloud_tmp_#{Time.now.to_i}")
|
235
|
+
super(make_commands(os, tmp_folder), debug, false)
|
236
|
+
end
|
237
|
+
|
238
|
+
def make_commands(os, tmp_folder)
|
239
|
+
cmds = []
|
240
|
+
case os
|
241
|
+
when /ubuntu/, /debian/
|
242
|
+
cmds << 'apt-get clean'
|
243
|
+
|
244
|
+
when /centos/, /redhat/
|
245
|
+
cmds << 'yum clean all'
|
246
|
+
end
|
247
|
+
cmds << "rm -rf #{tmp_folder}"
|
248
|
+
cmds
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class ScpTask < AvstCloud::Task
|
253
|
+
include Logging
|
254
|
+
|
255
|
+
def initialize(files)
|
256
|
+
@files = files
|
257
|
+
end
|
258
|
+
|
259
|
+
def execute(server)
|
260
|
+
unless server.ip_address
|
261
|
+
logger.error 'Can not find host'.red
|
262
|
+
raise 'Can not find ip address, access_user or access_password'
|
263
|
+
end
|
264
|
+
|
265
|
+
unless server.access_user
|
266
|
+
logger.error 'Access user not found. Please provide username for this server.'.red
|
267
|
+
raise 'Access user not found. Please provide username for this server.'
|
268
|
+
end
|
269
|
+
|
270
|
+
unless server.access_password
|
271
|
+
logger.error 'Password not found. Please provide password or pem key for this server.'.red
|
272
|
+
raise 'Password not found. Please provide root_password in config. for this server.'
|
273
|
+
end
|
274
|
+
|
275
|
+
logger.debug "Using #{server.access_user}@#{server.ip_address} with #{server.access_password}"
|
276
|
+
Net::SCP.start( server.ip_address, server.access_user, :password => server.access_password, :keys => [server.access_password] ) do |scp|
|
277
|
+
@files.each do |local_file, remote_path|
|
278
|
+
upload_file(scp, local_file, remote_path)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def upload_file(scp, local_path, remote_path)
|
284
|
+
logger.debug("Uploading file on server: #{local_path} to #{remote_path}")
|
285
|
+
scp.upload!( local_path, remote_path)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|