shiplane 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|