avst-cloud 0.1.7
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 +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
|