shiplane 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +73 -0
- data/lib/capistrano/shiplane/version.rb +5 -0
- data/lib/capistrano/shiplane.rb +6 -0
- data/lib/capistrano/tasks/capistrano_stubs.rake +122 -0
- data/lib/capistrano/tasks/shiplane.rake +16 -0
- data/lib/generators/shiplane/install/templates/Capfile.erb +13 -0
- data/lib/generators/shiplane/install/templates/deploy.rb.erb +132 -0
- data/lib/generators/shiplane/install/templates/production.rb.erb +61 -0
- data/lib/generators/shiplane/install/templates/production_dockerfile_stages.erb +44 -0
- data/lib/generators/shiplane/install/templates/shiplane.yml.erb +44 -0
- data/lib/shiplane/build.rb +101 -0
- data/lib/shiplane/checkout_artifact.rb +121 -0
- data/lib/shiplane/compose_hash.rb +38 -0
- data/lib/shiplane/configuration.rb +37 -0
- data/lib/shiplane/convert_compose_file.rb +59 -0
- data/lib/shiplane/convert_dockerfile.rb +60 -0
- data/lib/shiplane/deploy/configuration.rb +17 -0
- data/lib/shiplane/deploy/container_configuration.rb +93 -0
- data/lib/shiplane/deploy/network_configuration.rb +40 -0
- data/lib/shiplane/extensions.rb +49 -0
- data/lib/shiplane/host.rb +96 -0
- data/lib/shiplane/railtie.rb +5 -0
- data/lib/shiplane/tasks/install.rake +120 -0
- data/lib/shiplane/version.rb +5 -0
- data/lib/shiplane.rb +11 -0
- metadata +159 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Shiplane
|
4
|
+
class CheckoutArtifact
|
5
|
+
extend Forwardable
|
6
|
+
attr_accessor :sha
|
7
|
+
|
8
|
+
delegate %i(build_config project_config) => :shiplane_config
|
9
|
+
|
10
|
+
def initialize(sha)
|
11
|
+
@sha = sha
|
12
|
+
|
13
|
+
# call this before changing directories.
|
14
|
+
# This prevents race conditions where the config file is accessed before being downloaded
|
15
|
+
shiplane_config
|
16
|
+
end
|
17
|
+
|
18
|
+
def appname
|
19
|
+
@appname ||= project_config['appname']
|
20
|
+
end
|
21
|
+
|
22
|
+
def shiplane_config
|
23
|
+
@shiplane_config ||= Shiplane::Configuration.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def github_token
|
27
|
+
@github_token ||= ENV['GITHUB_TOKEN']
|
28
|
+
end
|
29
|
+
|
30
|
+
def git_url
|
31
|
+
"https://#{github_token ? "#{github_token}@" : ''}github.com/#{project_config['origin']}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def app_directory
|
35
|
+
@app_directory ||= File.join(Dir.pwd, 'docker_builds', appname)
|
36
|
+
end
|
37
|
+
|
38
|
+
def build_directory
|
39
|
+
@build_directory ||= File.join(app_directory, "#{appname}-#{sha}")
|
40
|
+
end
|
41
|
+
|
42
|
+
def make_directory
|
43
|
+
FileUtils.mkdir_p build_directory
|
44
|
+
end
|
45
|
+
|
46
|
+
def checkout!
|
47
|
+
return if File.exist?(File.join(build_directory, Shiplane::SHIPLANE_CONFIG_FILENAME))
|
48
|
+
|
49
|
+
puts "Checking out Application #{appname}[#{sha}]..."
|
50
|
+
make_directory
|
51
|
+
|
52
|
+
success = true
|
53
|
+
FileUtils.cd app_directory do
|
54
|
+
success = success && system("echo 'Downloading #{git_url}/archive/#{sha}.tar.gz --output #{appname}-#{sha}.tar.gz'")
|
55
|
+
success = success && system("curl -L #{git_url}/archive/#{sha}.tar.gz --output #{appname}-#{sha}.tar.gz")
|
56
|
+
success = success && system("tar -xzf #{appname}-#{sha}.tar.gz -C .")
|
57
|
+
end
|
58
|
+
|
59
|
+
raise "Errors encountered while downloading archive" unless success
|
60
|
+
puts "Finished checking out Application"
|
61
|
+
tasks.each(&method(:send))
|
62
|
+
end
|
63
|
+
|
64
|
+
def tasks
|
65
|
+
[:make_directories, :copy_env_files, :copy_insert_on_build_files, :unignore_required_directories]
|
66
|
+
end
|
67
|
+
|
68
|
+
def make_directories
|
69
|
+
FileUtils.cd build_directory do
|
70
|
+
required_directories.each do |directory|
|
71
|
+
FileUtils.mkdir_p directory
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def copy_env_files
|
77
|
+
puts "Copying in environment files..."
|
78
|
+
FileUtils.cp File.join(Dir.pwd, build_config.fetch('environment_file', '.env')), File.join(build_directory, '.env')
|
79
|
+
puts "Environment Files Copied"
|
80
|
+
end
|
81
|
+
|
82
|
+
def copy_insert_on_build_files
|
83
|
+
puts "Copying application configuration files..."
|
84
|
+
|
85
|
+
if Dir.exist? File.join(build_config.fetch('settings_folder', '.shiplane'), "insert_on_build")
|
86
|
+
FileUtils.cd File.join(build_config.fetch('settings_folder', '.shiplane'), "insert_on_build") do
|
87
|
+
Dir["*/**"].each do |filepath|
|
88
|
+
if File.extname(filepath) == ".erb"
|
89
|
+
copy_erb_file(filepath)
|
90
|
+
else
|
91
|
+
FileUtils.cp filepath, File.join(build_directory, filepath)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
puts "Configuration Files Copied"
|
97
|
+
end
|
98
|
+
|
99
|
+
def copy_erb_file(filepath)
|
100
|
+
File.write(File.join(build_directory, filepath.gsub(".erb","")), ERB.new(File.read(filepath)).result, mode: 'w')
|
101
|
+
end
|
102
|
+
|
103
|
+
def unignore_required_directories
|
104
|
+
puts "Adding Required Directories as explicit inclusions in ignore file..."
|
105
|
+
File.open(File.join(build_directory, '.dockerignore'), 'a') do |file|
|
106
|
+
required_directories.each do |directory|
|
107
|
+
file.puts "!#{directory}/*"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
puts "Finished including required directories..."
|
111
|
+
end
|
112
|
+
|
113
|
+
def required_directories
|
114
|
+
['vendor']
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.checkout!(sha)
|
118
|
+
new(sha).checkout!
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'extensions'
|
2
|
+
|
3
|
+
module Shiplane
|
4
|
+
class ComposeHash
|
5
|
+
attr_accessor :compose_file, :production_config
|
6
|
+
|
7
|
+
def initialize(compose_file, production_config)
|
8
|
+
@compose_file = compose_file
|
9
|
+
@production_config = production_config
|
10
|
+
end
|
11
|
+
|
12
|
+
def production_yml
|
13
|
+
blacklisted_nodes.inject(whitelisted_hash){ |acc, node| acc.blacklist(node) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def compose_hash
|
17
|
+
@compose_hash ||= YAML.load(compose_file)
|
18
|
+
end
|
19
|
+
|
20
|
+
def whitelisted_hash
|
21
|
+
@whitelisted_hash ||= compose_hash.whitelist(*default_whitelisted_nodes, *whitelisted_nodes)
|
22
|
+
end
|
23
|
+
|
24
|
+
def blacklisted_nodes
|
25
|
+
@blacklisted_nodes ||= production_config.fetch('blacklist', [])
|
26
|
+
end
|
27
|
+
|
28
|
+
def whitelisted_nodes
|
29
|
+
@whitelisted_nodes ||= production_config.fetch('whitelist', [])
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_whitelisted_nodes
|
33
|
+
[
|
34
|
+
"version",
|
35
|
+
]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Shiplane
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :project_folder
|
4
|
+
|
5
|
+
def initialize(project_folder = nil)
|
6
|
+
@project_folder = project_folder || Dir.pwd
|
7
|
+
end
|
8
|
+
|
9
|
+
def shiplane_config_file
|
10
|
+
@shiplane_config_file ||= File.join(project_folder, Shiplane::SHIPLANE_CONFIG_FILENAME)
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@config ||= YAML.load_file(shiplane_config_file)
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_config
|
18
|
+
@build_config ||= config.fetch('build', {})
|
19
|
+
end
|
20
|
+
|
21
|
+
def bootstrap_config
|
22
|
+
@bootstrap_config ||= config.fetch('bootstrap', {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def deploy_config
|
26
|
+
@deploy_config ||= config.fetch('deploy', {})
|
27
|
+
end
|
28
|
+
|
29
|
+
def project_config
|
30
|
+
@project_config ||= config.fetch('project', {})
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.config(project_folder = nil)
|
34
|
+
new(project_folder).config
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'facets/hash/traverse'
|
2
|
+
require_relative 'configuration'
|
3
|
+
require_relative 'compose_hash'
|
4
|
+
|
5
|
+
module Shiplane
|
6
|
+
class ConvertComposeFile
|
7
|
+
extend Forwardable
|
8
|
+
attr_accessor :project_folder, :sha
|
9
|
+
|
10
|
+
delegate %i(build_config) => :shiplane_config
|
11
|
+
|
12
|
+
def initialize(project_folder, sha)
|
13
|
+
@project_folder = project_folder
|
14
|
+
@sha = sha
|
15
|
+
end
|
16
|
+
|
17
|
+
def shiplane_config
|
18
|
+
@shiplane_config ||= Shiplane::Configuration.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def compose_config
|
22
|
+
@compose_config ||= build_config.fetch('compose', {})
|
23
|
+
end
|
24
|
+
|
25
|
+
def compose_filepath
|
26
|
+
@compose_filepath ||= File.join(project_folder, build_config.fetch('compose_filepath', Shiplane::DEFAULT_COMPOSEFILE_FILEPATH))
|
27
|
+
end
|
28
|
+
|
29
|
+
def converted_compose_hash
|
30
|
+
@converted_compose_hash ||= Shiplane::ComposeHash.new(File.new(compose_filepath), compose_config).production_yml
|
31
|
+
end
|
32
|
+
|
33
|
+
def converted_output
|
34
|
+
@converted_output ||= converted_compose_hash.dup.tap do |hash|
|
35
|
+
build_config.fetch('artifacts', {}).each do |(appname, config)|
|
36
|
+
hash.deep_merge!({ 'services' => { appname => { 'image' => "#{config['repo']}:#{sha}" } } })
|
37
|
+
end
|
38
|
+
|
39
|
+
hash.traverse! do |key, value|
|
40
|
+
if (key == 'env_file' && value == '.env.development')
|
41
|
+
[key, '.env.production']
|
42
|
+
else
|
43
|
+
[key, value]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def convert_output!
|
50
|
+
puts "Converting Compose File..."
|
51
|
+
File.write(compose_filepath, converted_output.to_yaml)
|
52
|
+
puts "Compose File Converted..."
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.convert_output!(project_folder, sha)
|
56
|
+
new(project_folder, sha).convert_output!
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative 'configuration'
|
2
|
+
|
3
|
+
module Shiplane
|
4
|
+
class ConvertDockerfile
|
5
|
+
extend Forwardable
|
6
|
+
attr_accessor :artifact_context, :compose_context, :project_folder
|
7
|
+
|
8
|
+
delegate %i(build_config project_config) => :shiplane_config
|
9
|
+
|
10
|
+
def initialize(project_folder, artifact_context, compose_context)
|
11
|
+
@project_folder = project_folder
|
12
|
+
@artifact_context = artifact_context
|
13
|
+
@compose_context = compose_context
|
14
|
+
end
|
15
|
+
|
16
|
+
def appname
|
17
|
+
@appname ||= project_config['appname']
|
18
|
+
end
|
19
|
+
|
20
|
+
def shiplane_config
|
21
|
+
@shiplane_config ||= Shiplane::Configuration.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def dockerfile_name
|
25
|
+
@dockerfile_name ||= compose_context.fetch('build', {}).fetch('context', '.').tap do |filename|
|
26
|
+
filename.gsub!(/^\.$/, 'Dockerfile')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def dockerfile_filepath
|
31
|
+
@dockerfile_filepath ||= File.join(project_folder, dockerfile_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def dockerfile_production_stages_filepath
|
35
|
+
@dockerfile_production_stages_filepath ||= File.join(Dir.pwd, build_config.fetch('settings_folder', '.shiplane'), Shiplane::DEFAULT_PRODUCTION_DOCKERFILE_STAGES_FILEPATH)
|
36
|
+
end
|
37
|
+
|
38
|
+
def entrypoint
|
39
|
+
@entrypoint ||= artifact_context.fetch('command', compose_context.fetch('command', "bin/rails s"))
|
40
|
+
end
|
41
|
+
|
42
|
+
def converted_output
|
43
|
+
@converted_output ||= [
|
44
|
+
File.read(dockerfile_filepath),
|
45
|
+
File.read(dockerfile_production_stages_filepath),
|
46
|
+
# "ENTRYPOINT #{entrypoint}",
|
47
|
+
].join("\n\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def convert_output!
|
51
|
+
puts "Converting Dockerfile..."
|
52
|
+
File.write(dockerfile_filepath, converted_output)
|
53
|
+
puts "Dockerfile Converted..."
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.convert_output!(project_folder, artifact_context, compose_context)
|
57
|
+
new(project_folder, artifact_context, compose_context).convert_output!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Shiplane
|
2
|
+
module Deploy
|
3
|
+
class Configuration
|
4
|
+
attr_accessor :env, :name, :options
|
5
|
+
|
6
|
+
def initialize(name, options, env)
|
7
|
+
@name = name
|
8
|
+
@options = options
|
9
|
+
@env = env
|
10
|
+
end
|
11
|
+
|
12
|
+
def docker_command(role)
|
13
|
+
role.requires_sudo? ? "sudo docker" : "docker"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative 'configuration'
|
2
|
+
|
3
|
+
module Shiplane
|
4
|
+
module Deploy
|
5
|
+
class ContainerConfiguration < Configuration
|
6
|
+
def network_alias
|
7
|
+
@network_alias ||= options.fetch(:alias, container_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def volumes
|
11
|
+
@volumes ||= options.fetch(:volumes, [])
|
12
|
+
end
|
13
|
+
|
14
|
+
def published_ports
|
15
|
+
@published_ports ||= [options.fetch(:publish, [])].flatten
|
16
|
+
end
|
17
|
+
|
18
|
+
def exposed_ports
|
19
|
+
@exposed_ports ||= [options.fetch(:expose, [])].flatten - published_ports
|
20
|
+
rescue => e
|
21
|
+
binding.pry
|
22
|
+
end
|
23
|
+
|
24
|
+
def container_name
|
25
|
+
@container_name ||= "#{env.fetch(:application)}_#{name}_#{env.fetch(:sha)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def image_name
|
29
|
+
@image_name ||= "#{options.fetch(:repo)}:#{image_tag}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def image_tag
|
33
|
+
@image_tag ||= options.fetch(:tag, "#{env.fetch(:stage)}-#{env.fetch(:sha)}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def virtual_host
|
37
|
+
@virtual_host ||= options[:virtual_host]
|
38
|
+
end
|
39
|
+
|
40
|
+
def letsencrypt_host
|
41
|
+
@letsencrypt_host ||= options.fetch(:letsencrypt_host, virtual_host)
|
42
|
+
end
|
43
|
+
|
44
|
+
def letsencrypt_email
|
45
|
+
@letsencrypt_email ||= options[:letsencrypt_email]
|
46
|
+
end
|
47
|
+
|
48
|
+
def networks
|
49
|
+
@networks ||= options.fetch(:networks, [])
|
50
|
+
end
|
51
|
+
|
52
|
+
def startup_command
|
53
|
+
@startup_command ||= options[:command]
|
54
|
+
end
|
55
|
+
|
56
|
+
def network_connect_commands(role)
|
57
|
+
@network_commands ||= networks.map do |network|
|
58
|
+
[
|
59
|
+
docker_command(role),
|
60
|
+
"network connect",
|
61
|
+
"--alias #{network_alias}",
|
62
|
+
network,
|
63
|
+
container_name,
|
64
|
+
"|| true",
|
65
|
+
].flatten.compact.join(" ")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_command(role)
|
70
|
+
@command ||= [
|
71
|
+
docker_command(role),
|
72
|
+
"run -d",
|
73
|
+
volumes.map{|volume_set| "-v #{volume_set}" },
|
74
|
+
published_ports.map{|port| "--expose #{port} -p #{port}" },
|
75
|
+
exposed_ports.map{|port| "--expose #{port}" },
|
76
|
+
"--name #{container_name}",
|
77
|
+
virtual_host ? "-e VIRTUAL_HOST=#{virtual_host}" : nil,
|
78
|
+
letsencrypt_host ? "-e LETSENCRYPT_HOST=#{letsencrypt_host}" : nil,
|
79
|
+
letsencrypt_email ? "-e LETSENCRYPT_EMAIL=#{letsencrypt_email}" : nil,
|
80
|
+
image_name,
|
81
|
+
startup_command ? startup_command : nil,
|
82
|
+
].flatten.compact.join(" ")
|
83
|
+
end
|
84
|
+
|
85
|
+
def run_commands(role)
|
86
|
+
@run_commands ||= [
|
87
|
+
run_command(role),
|
88
|
+
network_connect_commands(role),
|
89
|
+
].flatten
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'configuration'
|
2
|
+
|
3
|
+
module Shiplane
|
4
|
+
module Deploy
|
5
|
+
class NetworkConfiguration < Configuration
|
6
|
+
def connections
|
7
|
+
@connections ||= options.fetch(:connections, [])
|
8
|
+
end
|
9
|
+
|
10
|
+
def connect_commands(role)
|
11
|
+
@connect_commands ||=
|
12
|
+
connections.map do |connection|
|
13
|
+
[
|
14
|
+
docker_command(role),
|
15
|
+
"network connect",
|
16
|
+
name,
|
17
|
+
connection,
|
18
|
+
"|| true",
|
19
|
+
].flatten.compact.join(" ")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_command(role)
|
24
|
+
@create_command ||= [
|
25
|
+
docker_command(role),
|
26
|
+
"network create",
|
27
|
+
name,
|
28
|
+
"|| true",
|
29
|
+
].flatten.compact.join(" ")
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_commands(role)
|
33
|
+
[
|
34
|
+
create_command(role),
|
35
|
+
connect_commands(role),
|
36
|
+
].flatten
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'facets/hash/deep_merge'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def whitelist(*keymaps)
|
5
|
+
self.dup.whitelist!(*keymaps)
|
6
|
+
end
|
7
|
+
|
8
|
+
def whitelist!(*keymaps)
|
9
|
+
keymaps.map do |map|
|
10
|
+
deep_subset(map)
|
11
|
+
end.inject(&:deep_merge)
|
12
|
+
end
|
13
|
+
|
14
|
+
def deep_subset(keymap)
|
15
|
+
self.dup.deep_subset!(keymap)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deep_subset!(keymap)
|
19
|
+
keypath_keys = keymap.split('.')
|
20
|
+
return {} unless keypath_keys.size >= 1
|
21
|
+
|
22
|
+
deepest_subset = { keypath_keys.last => dig(*keypath_keys) }
|
23
|
+
|
24
|
+
keypath_keys[0..-2].reverse.inject(deepest_subset)do |accum, key|
|
25
|
+
accum = { key => accum }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def blacklist(keymap)
|
30
|
+
self.dup.blacklist!(keymap)
|
31
|
+
end
|
32
|
+
|
33
|
+
def blacklist!(keymap, parentparts = nil)
|
34
|
+
keypart, *rest = keymap.split(".")
|
35
|
+
keychain = [parentparts, keypart].compact.join(".")
|
36
|
+
|
37
|
+
self.each do |k, v|
|
38
|
+
v.blacklist!(Array(rest).join("."), keychain) if v.is_a?(Hash) && k.to_s == keypart.to_s
|
39
|
+
self.delete(k) if k.to_s == keypart.to_s && rest.empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Array
|
45
|
+
def pad(pad_length, character = nil)
|
46
|
+
return self if size >= pad_length
|
47
|
+
self + [character] * (pad_length - size)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'sshkit'
|
2
|
+
require 'sshkit/dsl'
|
3
|
+
|
4
|
+
module Shiplane
|
5
|
+
class Host
|
6
|
+
extend Forwardable
|
7
|
+
include SSHKit::DSL
|
8
|
+
|
9
|
+
attr_accessor :env, :host, :role
|
10
|
+
def_delegators :env, :servers
|
11
|
+
def_delegators :role, :hostname
|
12
|
+
|
13
|
+
SSHKIT_PROPERTIES = %i(user password keys hostname port ssh_options)
|
14
|
+
|
15
|
+
def initialize(role, env)
|
16
|
+
@role = role
|
17
|
+
@env = env
|
18
|
+
end
|
19
|
+
|
20
|
+
def host
|
21
|
+
@host ||= SSHKit::Host.new(sshkit_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def capistrano_role
|
25
|
+
@capistrano_role ||= role.dup.tap do |r|
|
26
|
+
r.properties.set(:ssh_options, ssh_options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def sshkit_values
|
31
|
+
{
|
32
|
+
interaction_handler: { "[sudo] password for #{user}: " => "#{password}\n" }
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def requires_sudo?
|
37
|
+
@requires_sudo ||= config.fetch('requires_sudo', false)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def user
|
43
|
+
ssh_options.fetch("user", "")
|
44
|
+
end
|
45
|
+
|
46
|
+
def password
|
47
|
+
ssh_options.fetch("password", "")
|
48
|
+
end
|
49
|
+
|
50
|
+
def sshkit_options
|
51
|
+
@sshkit_options ||= options.merge(hostname: hostname).slice(*SSHKIT_PROPERTIES)
|
52
|
+
end
|
53
|
+
|
54
|
+
def options
|
55
|
+
@options ||= role.properties.to_h.symbolize_keys.merge(ssh_options: ssh_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def ssh_options
|
59
|
+
@ssh_options ||= config.fetch('ssh_options', {}).symbolize_keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def config
|
63
|
+
self.class.config.fetch('deploy', {}).fetch('servers', {}).fetch(hostname, {})
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_context(&block)
|
67
|
+
set(:shiplane_sshkit_values, sshkit_values)
|
68
|
+
yield
|
69
|
+
set(:shiplane_sshkit_values, nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
def sshkit_output
|
73
|
+
@sshkit_output ||= SSHKit.config.output
|
74
|
+
end
|
75
|
+
|
76
|
+
def write_message(verbosity, message)
|
77
|
+
sshkit_output.write(SSHKit::LogMessage.new(verbosity, message))
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.env_file
|
81
|
+
config.fetch("bootstrap", {}).fetch('env_file', '.env')
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.config
|
85
|
+
@config ||= YAML.load(File.read(config_filepath))
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.config_filepath
|
89
|
+
File.join("shiplane.yml")
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.bootstrap!(host, env)
|
93
|
+
new(host, env).bootstrap!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|