phase 0.0.5 → 0.0.6
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 +4 -4
- data/VERSION +1 -1
- data/lib/phase/adapters/aws/server.rb +17 -7
- data/lib/phase/cli/all.rb +2 -2
- data/lib/phase/cli/command.rb +1 -0
- data/lib/phase/cli/deploy.rb +36 -0
- data/lib/phase/cli/ipa.rb +46 -0
- data/lib/phase/cli/ssh.rb +0 -2
- data/lib/phase/configuration.rb +35 -31
- data/lib/phase/dsl.rb +20 -26
- data/lib/phase/kit/deploy/build.rb +46 -0
- data/lib/phase/kit/deploy/deployment.rb +26 -0
- data/lib/phase/kit/ipa/app.rb +71 -0
- data/lib/phase/kit/ipa/enterprise_deployment.rb +85 -0
- data/lib/phase/{ssh → kit/ssh}/backend.rb +6 -4
- data/lib/phase/kit/ssh/bastion_coordinator.rb +44 -0
- data/lib/phase/kit/ssh/runners.rb +30 -0
- data/lib/phase/tasks/deploy.rb +118 -0
- data/lib/phase/util/console.rb +15 -0
- data/lib/phase/version.rb +1 -1
- data/lib/phase.rb +18 -9
- data/phase.gemspec +1 -0
- metadata +29 -6
- data/lib/phase/cli/mixins/loggable.rb +0 -21
- /data/lib/phase/{keys → kit/keys}/key.rb +0 -0
- /data/lib/phase/{ssh → kit/ssh}/command.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0e05d1823d6db24fc626a2401e46661cc90f5ce
|
4
|
+
data.tar.gz: ed876996eda29eba97b94d827df8119d6cab93d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b6ab4a3dc89082fc6b769d5d5b302e7288ba6083312f8523118425721322949b8dca51bca9dbe1ae720285e76b2a228f4897040bf11e6c625b209c49f0d3904
|
7
|
+
data.tar.gz: 8ae0ec1e4d1a2b1267c9d4bf7445ddf4464bf45f296258b02e03bb416c360832c2208c74642a0ce24092004d04f223ca06ca3f6828399c6a7b18c55f5772429f
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.6
|
@@ -3,6 +3,15 @@ module Phase
|
|
3
3
|
module AWS
|
4
4
|
class Server < ::Phase::Adapters::Abstract::Server
|
5
5
|
|
6
|
+
attr_accessor :username
|
7
|
+
|
8
|
+
def to_host_hash(host_method)
|
9
|
+
{
|
10
|
+
user: username,
|
11
|
+
hostname: resource.__send__(host_method)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
6
15
|
class << self
|
7
16
|
# @return [Array<AWS::Server>] All known EC2 instances
|
8
17
|
def all
|
@@ -15,6 +24,8 @@ module Phase
|
|
15
24
|
new(api.servers.get(instance_id))
|
16
25
|
end
|
17
26
|
|
27
|
+
# Finds servers through the provided filter options.
|
28
|
+
#
|
18
29
|
# @param [Hash] options Filtering options
|
19
30
|
# @option options [String] :vpc_id The ID of a VPC
|
20
31
|
# @option options [String] :name The value of the 'Name' tag
|
@@ -26,13 +37,12 @@ module Phase
|
|
26
37
|
def where(options = {})
|
27
38
|
filters = {}
|
28
39
|
|
29
|
-
filters["vpc-id"] = options.delete(:vpc_id)
|
30
|
-
filters["tag:Name"] = options.delete(:name)
|
31
|
-
filters["instance-ids"] = options.delete(:ids)
|
32
|
-
filters["subnet-id"] = options.delete(:subnet_id)
|
33
|
-
|
34
|
-
filters["tag:
|
35
|
-
filters["tag:Environment"] = options.delete(:environment) if options[:environment]
|
40
|
+
filters["vpc-id"] = options.delete(:vpc_id) if options.has_key?(:vpc_id)
|
41
|
+
filters["tag:Name"] = options.delete(:name) if options.has_key?(:name)
|
42
|
+
filters["instance-ids"] = options.delete(:ids) if options.has_key?(:ids)
|
43
|
+
filters["subnet-id"] = options.delete(:subnet_id) if options.has_key?(:subnet_id)
|
44
|
+
filters["tag:Role"] = options.delete(:role) if options.has_key?(:role)
|
45
|
+
filters["tag:Environment"] = options.delete(:environment) if options.has_key?(:environment)
|
36
46
|
|
37
47
|
if options.any?
|
38
48
|
raise ArgumentError, "Unknown filters '#{options.keys.join(", ")}'!"
|
data/lib/phase/cli/all.rb
CHANGED
data/lib/phase/cli/command.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Phase
|
2
|
+
module CLI
|
3
|
+
class Deploy < Command
|
4
|
+
|
5
|
+
command :deploy do |c|
|
6
|
+
c.syntax = "phase deploy <environment>"
|
7
|
+
|
8
|
+
c.description = <<-EOS.strip_heredoc
|
9
|
+
Builds and deploys code to the specified <environment>. <environment> may be
|
10
|
+
any environment configured in the Phasefile.
|
11
|
+
EOS
|
12
|
+
|
13
|
+
c.action do |args, options|
|
14
|
+
new(args, options).run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :environment
|
19
|
+
|
20
|
+
def initialize(args, options)
|
21
|
+
@environment = args.first
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
# load_environment_config
|
27
|
+
# create_build_dir
|
28
|
+
# shallow_clone_repository
|
29
|
+
# build_docker_image
|
30
|
+
# push_docker_image
|
31
|
+
# trigger_remote_deployment
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Phase
|
2
|
+
module CLI
|
3
|
+
class IPA < Command
|
4
|
+
|
5
|
+
command :ipa do |c|
|
6
|
+
c.syntax = "phase ipa [version_number] [filename|pattern]..."
|
7
|
+
|
8
|
+
c.description = <<-EOS.strip_heredoc
|
9
|
+
Generates enterprise distribution .plists for .ipa app bundles and uploads
|
10
|
+
all required files to the web for distribution.
|
11
|
+
EOS
|
12
|
+
|
13
|
+
c.action do |args, options|
|
14
|
+
new(args, options).run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :version, :filenames
|
19
|
+
|
20
|
+
def initialize(args, options)
|
21
|
+
@version = args.shift
|
22
|
+
@filenames = args
|
23
|
+
|
24
|
+
if @version.blank? || @filenames.blank?
|
25
|
+
fail "invalid syntax: phase ipa [--version version_number] [filename|glob_pattern]..."
|
26
|
+
end
|
27
|
+
|
28
|
+
if Phase.config.bundle_id_prefix.blank?
|
29
|
+
fail "missing setting: set `Phase.config.bundle_id_prefix = [PREFIX] in Phasefile"
|
30
|
+
elsif Phase.config.ipa_bucket_name.blank?
|
31
|
+
fail "missing setting: set `Phase.config.ipa_bucket_name = [BUCKET]` in Phasefile"
|
32
|
+
elsif Phase.config.ipa_directory_prefix.blank?
|
33
|
+
fail "missing setting: set `Phase.config.ipa_directory_prefix = [PREFIX] in Phasefile"
|
34
|
+
end
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def run
|
40
|
+
deployment = ::Phase::IPA::EnterpriseDeployment.new(version, *filenames)
|
41
|
+
deployment.run!
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/phase/cli/ssh.rb
CHANGED
data/lib/phase/configuration.rb
CHANGED
@@ -1,45 +1,49 @@
|
|
1
1
|
module Phase
|
2
2
|
class Configuration
|
3
3
|
|
4
|
-
attr_accessor :
|
5
|
-
:
|
6
|
-
:
|
7
|
-
|
8
|
-
:aws_region,
|
9
|
-
|
10
|
-
:adapter
|
4
|
+
attr_accessor :bastions_enabled, :bastion_role, :bastion_user, :public_subnet_name,
|
5
|
+
:private_subnet_name, :aws_region, :adapter, :backend,
|
6
|
+
:bundle_id_prefix, :ipa_directory_prefix, :ipa_bucket_name
|
11
7
|
|
12
8
|
def initialize
|
13
|
-
@
|
14
|
-
@bastion_role
|
15
|
-
@bastion_user
|
16
|
-
|
17
|
-
@
|
18
|
-
|
19
|
-
@adapter
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
9
|
+
@bastions_enabled = false
|
10
|
+
@bastion_role = nil
|
11
|
+
@bastion_user = nil
|
12
|
+
@public_subnet_name = "public"
|
13
|
+
@private_subnet_name = "private"
|
14
|
+
@aws_region = "us-east-1"
|
15
|
+
@adapter = ::Phase::Adapters::AWS
|
16
|
+
|
17
|
+
@bundle_id_prefix = ""
|
18
|
+
@ipa_directory_prefix = ""
|
19
|
+
@ipa_bucket_name = ""
|
20
|
+
|
21
|
+
self.backend = ::Phase::SSH::Backend
|
22
|
+
set_aws_credentials!
|
24
23
|
end
|
25
24
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
@use_bastions = yml_config[:use_bastions] if yml_config.has_key(:use_bastions)
|
30
|
-
@bastion_role = yml_config[:bastion_role] if yml_config.has_key(:bastion_role)
|
31
|
-
@bastion_user = yml_config[:bastion_user] if yml_config.has_key(:bastion_user)
|
32
|
-
|
33
|
-
@aws_region = yml_config[:aws_region] if yml_config.has_key(:aws_region)
|
25
|
+
def backend=(backend)
|
26
|
+
@backend = backend
|
27
|
+
::SSHKit.config.backend = @backend
|
34
28
|
end
|
35
29
|
|
36
|
-
def
|
37
|
-
File.
|
30
|
+
def load_phasefile!
|
31
|
+
if ::File.exist?(phasefile_path)
|
32
|
+
load phasefile_path
|
33
|
+
end
|
38
34
|
end
|
39
35
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
36
|
+
def set_aws_credentials!
|
37
|
+
Fog.credentials = {
|
38
|
+
aws_access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
|
39
|
+
aws_secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY')
|
40
|
+
}
|
43
41
|
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def phasefile_path
|
46
|
+
@phasefile_path ||= ::File.join(::Dir.pwd, 'Phasefile')
|
47
|
+
end
|
44
48
|
end
|
45
49
|
end
|
data/lib/phase/dsl.rb
CHANGED
@@ -1,37 +1,31 @@
|
|
1
|
+
require 'sshkit/dsl'
|
2
|
+
|
1
3
|
module Phase
|
2
4
|
module DSL
|
3
5
|
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# @param [
|
6
|
+
# Runs a command on servers with the given role.
|
7
|
+
#
|
8
|
+
# @param [String] role_name The value of the 'Role' tag. Can be nil
|
9
|
+
# @param [String] environment The value of the 'Environment' tag. Can be nil
|
10
|
+
# @param [Hash] options Query and concurrency options
|
11
|
+
# @option options [String] :bastion (true) Whether to connect through a bastion host
|
7
12
|
# @see SSHKit::Coordinator for concurrency options
|
8
13
|
# @return [void]
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
bastion_host = "#{ ::Phase.config.bastion_user }@#{ server.resource.dns_name }"
|
20
|
-
coordinator = ::SSHKit::Coordinator.new(bastion_host)
|
21
|
-
|
22
|
-
# TODO: clean up this logic. this should be done within a coordinator
|
23
|
-
# (or elsewhere) so we can ID networks on a per-adapter basis
|
24
|
-
results = Array(destination_ips).map do |ip|
|
25
|
-
coordinator.each(options) do
|
26
|
-
on_remote_host(ip) { instance_exec(&block) }
|
27
|
-
end
|
14
|
+
def run_remotely(options = {}, &block)
|
15
|
+
filter_options = options.extract!(:role_name, :environment, :user)
|
16
|
+
servers = ::Phase.servers.where(filter_options)
|
17
|
+
|
18
|
+
if options.has_key?(:bastion) && bastion = options.delete(:bastion)
|
19
|
+
hosts = servers.map { |s| {user: options[:user], hostname: s.resource.private_ip_address} }
|
20
|
+
on_servers_through_bastion(bastion, servers, options, &block)
|
21
|
+
else
|
22
|
+
hosts = servers.map { |s| {user: options[:user], hostname: s.resource.dns_name} }
|
23
|
+
on(hosts, options, &block)
|
28
24
|
end
|
29
|
-
|
30
|
-
results.flatten
|
31
25
|
end
|
32
26
|
|
33
|
-
def
|
34
|
-
::
|
27
|
+
def on_servers_through_bastion(bastion, servers, options = {}, &block)
|
28
|
+
::Phase::Kit::SSH::BastionCoordinator.new(bastion, hosts).run!(options, &block)
|
35
29
|
end
|
36
30
|
|
37
31
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Phase
|
2
|
+
module Deploy
|
3
|
+
|
4
|
+
class Build
|
5
|
+
attr_reader :version_tag
|
6
|
+
|
7
|
+
def initialize(version_tag)
|
8
|
+
@version_tag = version_tag
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
build_image
|
13
|
+
tag_latest_image
|
14
|
+
push
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_image
|
20
|
+
clear_build_dir
|
21
|
+
clone_clean_repo
|
22
|
+
system("docker build -t #{repo_name}:#{version_tag} .build/")
|
23
|
+
end
|
24
|
+
|
25
|
+
def tag_latest_image
|
26
|
+
system("docker tag #{repo_name}:#{version_tag} #{repo_name}:latest")
|
27
|
+
end
|
28
|
+
|
29
|
+
def push
|
30
|
+
system("docker push #{repo_name}:#{version_tag}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def repo_name
|
34
|
+
::Phase.config.docker_repository
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class SandboxBuild < Build
|
40
|
+
def build
|
41
|
+
system(build_image_cmd)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Phase
|
2
|
+
module Deploy
|
3
|
+
class Deployment
|
4
|
+
|
5
|
+
# environment
|
6
|
+
# build options
|
7
|
+
# tag strategy
|
8
|
+
# server options
|
9
|
+
# environment name
|
10
|
+
# role name
|
11
|
+
|
12
|
+
attr_reader :build
|
13
|
+
|
14
|
+
def initialize(environment, options = {})
|
15
|
+
case environment
|
16
|
+
when :sandbox
|
17
|
+
self.build = SandboxBuild.new
|
18
|
+
else
|
19
|
+
self.build = Build.new
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Phase
|
2
|
+
module IPA
|
3
|
+
class App
|
4
|
+
attr_reader :qualified_path, :name, :version
|
5
|
+
attr_accessor :download_url
|
6
|
+
|
7
|
+
def initialize(qualified_path, version)
|
8
|
+
@qualified_path, @version = qualified_path, version
|
9
|
+
@name = ::File.basename(qualified_path, ".ipa")
|
10
|
+
end
|
11
|
+
|
12
|
+
def ipa_filename
|
13
|
+
"#{bundle_name}-#{version}.ipa"
|
14
|
+
end
|
15
|
+
|
16
|
+
def plist_filename
|
17
|
+
"#{bundle_name}-#{version}.plist"
|
18
|
+
end
|
19
|
+
|
20
|
+
def plist_xml
|
21
|
+
<<-EOXML.strip_heredoc
|
22
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
23
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
24
|
+
<plist version="1.0">
|
25
|
+
<dict>
|
26
|
+
<key>items</key>
|
27
|
+
<array>
|
28
|
+
<dict>
|
29
|
+
<key>assets</key>
|
30
|
+
<array>
|
31
|
+
<dict>
|
32
|
+
<key>kind</key>
|
33
|
+
<string>software-package</string>
|
34
|
+
<key>url</key>
|
35
|
+
<string>https://s3.amazonaws.com/orca-static-assets/apps/#{ipa_filename}</string>
|
36
|
+
</dict>
|
37
|
+
</array>
|
38
|
+
<key>metadata</key>
|
39
|
+
<dict>
|
40
|
+
<key>bundle-identifier</key>
|
41
|
+
<string>#{bundle_id_prefix}.#{bundle_name}</string>
|
42
|
+
<key>bundle-version</key>
|
43
|
+
<string>#{version}</string>
|
44
|
+
<key>kind</key>
|
45
|
+
<string>software</string>
|
46
|
+
<key>title</key>
|
47
|
+
<string>#{human_name}</string>
|
48
|
+
</dict>
|
49
|
+
</dict>
|
50
|
+
</array>
|
51
|
+
</dict>
|
52
|
+
</plist>
|
53
|
+
EOXML
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def human_name
|
59
|
+
name.titleize
|
60
|
+
end
|
61
|
+
|
62
|
+
def bundle_name
|
63
|
+
name.camelize
|
64
|
+
end
|
65
|
+
|
66
|
+
def bundle_id_prefix
|
67
|
+
Phase.config.bundle_id_prefix
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Phase
|
2
|
+
module IPA
|
3
|
+
class EnterpriseDeployment
|
4
|
+
include ::Phase::Util::Console
|
5
|
+
|
6
|
+
attr_reader :version, :file_paths, :aws, :bucket, :prefix, :apps
|
7
|
+
|
8
|
+
def initialize(version, *filenames)
|
9
|
+
@version = version
|
10
|
+
@file_paths = filenames.map do |name|
|
11
|
+
::Dir.glob( ::File.expand_path(name) )
|
12
|
+
end
|
13
|
+
|
14
|
+
@file_paths.flatten!
|
15
|
+
@file_paths.uniq!
|
16
|
+
|
17
|
+
@aws = ::Fog::Storage::AWS.new(region: Phase.config.aws_region)
|
18
|
+
@bucket = aws.directories.get(Phase.config.ipa_bucket_name)
|
19
|
+
@prefix = ::Pathname.new(Phase.config.ipa_directory_prefix)
|
20
|
+
|
21
|
+
@apps = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def run!
|
25
|
+
::FileUtils.mkdir(version) rescue nil
|
26
|
+
|
27
|
+
@apps = file_paths.map do |path|
|
28
|
+
app = App.new(path, version)
|
29
|
+
write_plist!(app)
|
30
|
+
copy_ipa!(app)
|
31
|
+
upload!(app)
|
32
|
+
end
|
33
|
+
|
34
|
+
write_manifest!
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_manifest!
|
38
|
+
::File.join(::Dir.pwd, version, "manifest.txt") do |file|
|
39
|
+
apps.each { |app| file << app.install_link }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_plist!(app)
|
44
|
+
log "#{app.name}: writing .plist"
|
45
|
+
::File.open(plist_path(app), 'w') { |file| file << app.plist_xml }
|
46
|
+
end
|
47
|
+
|
48
|
+
def copy_ipa!(app)
|
49
|
+
log "#{app.name}: copying .ipa"
|
50
|
+
::FileUtils.cp(app.qualified_path, ipa_path(app))
|
51
|
+
end
|
52
|
+
|
53
|
+
def upload!(app)
|
54
|
+
ipa = bucket.files.new({
|
55
|
+
key: prefix.join(app.ipa_filename),
|
56
|
+
body: ::File.open(ipa_path(app)),
|
57
|
+
acl: "public-read"
|
58
|
+
})
|
59
|
+
|
60
|
+
plist = bucket.files.new({
|
61
|
+
key: prefix.join(app.plist_filename),
|
62
|
+
body: ::File.open(plist_path(app)),
|
63
|
+
acl: "public-read"
|
64
|
+
})
|
65
|
+
|
66
|
+
log "#{app.name}: uploading .ipa"
|
67
|
+
ipa.save
|
68
|
+
|
69
|
+
log "#{app.name}: uploading .plist"
|
70
|
+
plist.save
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def plist_path(app)
|
76
|
+
::File.join(::Dir.pwd, version, app.plist_filename)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ipa_path(app)
|
80
|
+
::File.join(::Dir.pwd, version, app.ipa_filename)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
module Phase
|
2
2
|
module SSH
|
3
3
|
class Backend < ::SSHKit::Backend::Netssh
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(*args)
|
4
|
+
def initialize(host, options = {})
|
7
5
|
# BUG: Backend::Netssh doesn't assign @pool when subclassed.
|
8
6
|
self.class.pool = ::SSHKit::Backend::ConnectionPool.new
|
9
|
-
|
7
|
+
@host = host
|
10
8
|
end
|
11
9
|
|
12
10
|
def on_remote_host(remote_host, &block)
|
@@ -14,6 +12,10 @@ module Phase
|
|
14
12
|
yield
|
15
13
|
end
|
16
14
|
|
15
|
+
def run(&block)
|
16
|
+
instance_exec(host, &block)
|
17
|
+
end
|
18
|
+
|
17
19
|
private
|
18
20
|
|
19
21
|
def command(*args)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Phase
|
2
|
+
module SSH
|
3
|
+
class BastionCoordinator
|
4
|
+
|
5
|
+
def initialize(bastion, destination_hosts)
|
6
|
+
@bastion = bastion
|
7
|
+
@destination_hosts = destination_hosts
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(options = {}, &block)
|
11
|
+
if hosts
|
12
|
+
queue = Queue.new
|
13
|
+
@destination_hosts.each {|host| queue << hosts }
|
14
|
+
|
15
|
+
options = default_options.merge(options)
|
16
|
+
options[:address_queue] = queue
|
17
|
+
|
18
|
+
case options[:in]
|
19
|
+
when :parallel then ::Phase::SSH::Runners::Parallel
|
20
|
+
when :sequence then ::Phase::SSH::Runners::Sequential
|
21
|
+
when :groups then ::Phase::SSH::Runners::Group
|
22
|
+
else
|
23
|
+
raise RuntimeError, "Don't know how to handle run style #{options[:in].inspect}"
|
24
|
+
end.new([@bastion] * @destination_hosts.count, options, &block).execute
|
25
|
+
else
|
26
|
+
Runners::Null.new(hosts, options, &block).execute
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# def run!(options = {}, &block)
|
31
|
+
# backend = Backend.new(@bastion, options)
|
32
|
+
#
|
33
|
+
# results = @destination_hosts.each do |host|
|
34
|
+
# backend.run do
|
35
|
+
# on_remote_host(ip) { instance_exec(&block) }
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# results.flatten
|
40
|
+
# end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Phase
|
2
|
+
module SSH
|
3
|
+
module Runners
|
4
|
+
|
5
|
+
module BastionRunner
|
6
|
+
def backend(host, &block)
|
7
|
+
backend = ::Phase.config.backend.new(host)
|
8
|
+
address = options[:address_queue].pop
|
9
|
+
|
10
|
+
backend.run do
|
11
|
+
on_remote_host(address) { instance_exec(&block) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Parallel < ::SSHKit::Runner::Parallel
|
17
|
+
include BastionRunner
|
18
|
+
end
|
19
|
+
|
20
|
+
class Sequential < ::SSHKit::Runner::Sequential
|
21
|
+
include BastionRunner
|
22
|
+
end
|
23
|
+
|
24
|
+
class Null < ::SSHKit::Runner::Null
|
25
|
+
include BastionRunner
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# require "phase/dsl"
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
task deploy: "deploy:all"
|
5
|
+
|
6
|
+
namespace :deploy do
|
7
|
+
task all: [
|
8
|
+
:verify_environment,
|
9
|
+
:set_version,
|
10
|
+
:build_assets,
|
11
|
+
:record_deployment
|
12
|
+
# :trigger_docker_build
|
13
|
+
# :deploy_to_target_servers_in_sequence
|
14
|
+
]
|
15
|
+
|
16
|
+
task verify_environment: :environment do
|
17
|
+
::Deploy.raise_on_incorrect_branch!
|
18
|
+
::Deploy.raise_on_dirty_index!
|
19
|
+
end
|
20
|
+
|
21
|
+
task set_version: :environment do
|
22
|
+
::Deploy.log "Last release was version #{ ::Deploy.current_version.magenta }"
|
23
|
+
next_version_num = ::Deploy.ask "New version number: "
|
24
|
+
::Deploy.write_version(next_version_num)
|
25
|
+
end
|
26
|
+
|
27
|
+
task build_assets: :environment do
|
28
|
+
::Deploy.precompile_assets
|
29
|
+
::Deploy.sync_assets
|
30
|
+
end
|
31
|
+
|
32
|
+
task record_deployment: :environment do
|
33
|
+
::Deploy.stage_changes!
|
34
|
+
::Deploy.commit_deployment!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
module Deploy
|
40
|
+
class << self
|
41
|
+
VERSION_LOCKFILE_PATH = "VERSION"
|
42
|
+
VERSION_RBFILE_PATH = "lib/harpoon/version.rb"
|
43
|
+
|
44
|
+
def ask(str)
|
45
|
+
print "[deploy] ".green + str
|
46
|
+
STDIN.gets.chomp
|
47
|
+
end
|
48
|
+
|
49
|
+
def commit_deployment!
|
50
|
+
system("git commit -m 'Preparing to release v#{ current_version }' -e")
|
51
|
+
end
|
52
|
+
|
53
|
+
def current_version
|
54
|
+
::File.open(VERSION_LOCKFILE_PATH) do |file|
|
55
|
+
file.read.chomp
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def environment
|
60
|
+
ARGV[1] || "staging"
|
61
|
+
end
|
62
|
+
|
63
|
+
def fail(str)
|
64
|
+
abort("[deploy] error: ".red + str)
|
65
|
+
end
|
66
|
+
|
67
|
+
def log(str)
|
68
|
+
puts "[deploy] ".green + str
|
69
|
+
end
|
70
|
+
|
71
|
+
def precompile_assets
|
72
|
+
system("RAILS_GROUPS=assets RAILS_ENV=#{ environment } rake assets:precompile")
|
73
|
+
end
|
74
|
+
|
75
|
+
def raise_on_dirty_index!
|
76
|
+
unless system('git diff-index --quiet --cached HEAD')
|
77
|
+
fail "other changes are already staged. Commit them or 'git reset HEAD' first."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def raise_on_incorrect_branch!
|
82
|
+
current_branch = `git rev-parse --abbrev-ref HEAD`.chomp
|
83
|
+
needed_branch = ::Deploy.environment == "staging" ? "develop" : "master"
|
84
|
+
|
85
|
+
if current_branch != needed_branch
|
86
|
+
fail "your current branch is #{current_branch}. Switch to #{needed_branch}."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def stage_changes!
|
91
|
+
files = [
|
92
|
+
::Rails.root.join("public", ::Rails.application.config.assets.prefix, "manifest*.json"),
|
93
|
+
::Rails.root.join(::Deploy::VERSION_LOCKFILE_PATH),
|
94
|
+
::Rails.root.join(::Deploy::VERSION_RBFILE_PATH)
|
95
|
+
]
|
96
|
+
|
97
|
+
system("git add #{ files.join(" ") }")
|
98
|
+
end
|
99
|
+
|
100
|
+
def sync_assets
|
101
|
+
system("RAILS_GROUPS=assets RAILS_ENV=#{ environment } rake assets:sync")
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_version(version_num)
|
105
|
+
::File.open(VERSION_LOCKFILE_PATH, 'w') do |file|
|
106
|
+
file.write(version_num)
|
107
|
+
end
|
108
|
+
|
109
|
+
::File.open(VERSION_RBFILE_PATH, 'w') do |file|
|
110
|
+
file.write <<-VERSION.strip_heredoc
|
111
|
+
module Harpoon
|
112
|
+
VERSION = "#{version_num}"
|
113
|
+
end
|
114
|
+
VERSION
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/phase/version.rb
CHANGED
data/lib/phase.rb
CHANGED
@@ -1,20 +1,27 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require 'progressbar'
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'capistrano'
|
4
3
|
require 'colorize'
|
5
4
|
require 'fog'
|
6
|
-
require '
|
5
|
+
require 'progressbar'
|
6
|
+
require 'pry'
|
7
7
|
require 'sshkit'
|
8
|
+
require 'terminal-table'
|
8
9
|
|
9
|
-
require 'dotenv'
|
10
|
-
::Dotenv.load if defined?(::Dotenv)
|
11
10
|
|
12
11
|
require 'phase/adapter'
|
13
12
|
require 'phase/adapters/abstract'
|
14
13
|
require 'phase/adapters/aws'
|
15
14
|
|
16
|
-
require
|
17
|
-
|
15
|
+
require "phase/util/console"
|
16
|
+
|
17
|
+
require 'phase/kit/ipa/app'
|
18
|
+
require 'phase/kit/ipa/enterprise_deployment'
|
19
|
+
|
20
|
+
require 'phase/kit/ssh/backend'
|
21
|
+
# require 'phase/kit/ssh/bastion'
|
22
|
+
require 'phase/kit/ssh/bastion_coordinator'
|
23
|
+
require 'phase/kit/ssh/command'
|
24
|
+
require 'phase/kit/ssh/runners'
|
18
25
|
|
19
26
|
require 'phase/configuration'
|
20
27
|
require 'phase/version'
|
@@ -41,5 +48,7 @@ module Phase
|
|
41
48
|
|
42
49
|
end
|
43
50
|
|
44
|
-
|
51
|
+
class ResourceNotFoundError < ::StandardError; end
|
45
52
|
end
|
53
|
+
|
54
|
+
Phase.config.load_phasefile!
|
data/phase.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piers Mainwaring
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-04-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: commander
|
@@ -165,6 +165,20 @@ dependencies:
|
|
165
165
|
- - "~>"
|
166
166
|
- !ruby/object:Gem::Version
|
167
167
|
version: '3.1'
|
168
|
+
- !ruby/object:Gem::Dependency
|
169
|
+
name: pry
|
170
|
+
requirement: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - "~>"
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0.10'
|
175
|
+
type: :development
|
176
|
+
prerelease: false
|
177
|
+
version_requirements: !ruby/object:Gem::Requirement
|
178
|
+
requirements:
|
179
|
+
- - "~>"
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0.10'
|
168
182
|
description: ''
|
169
183
|
email:
|
170
184
|
- piers@impossibly.org
|
@@ -196,18 +210,27 @@ files:
|
|
196
210
|
- lib/phase/cli.rb
|
197
211
|
- lib/phase/cli/all.rb
|
198
212
|
- lib/phase/cli/command.rb
|
213
|
+
- lib/phase/cli/deploy.rb
|
199
214
|
- lib/phase/cli/env.rb
|
215
|
+
- lib/phase/cli/ipa.rb
|
200
216
|
- lib/phase/cli/keys.rb
|
201
217
|
- lib/phase/cli/logs.rb
|
202
|
-
- lib/phase/cli/mixins/loggable.rb
|
203
218
|
- lib/phase/cli/mosh.rb
|
204
219
|
- lib/phase/cli/ssh.rb
|
205
220
|
- lib/phase/cli/status.rb
|
206
221
|
- lib/phase/configuration.rb
|
207
222
|
- lib/phase/dsl.rb
|
208
|
-
- lib/phase/
|
209
|
-
- lib/phase/
|
210
|
-
- lib/phase/
|
223
|
+
- lib/phase/kit/deploy/build.rb
|
224
|
+
- lib/phase/kit/deploy/deployment.rb
|
225
|
+
- lib/phase/kit/ipa/app.rb
|
226
|
+
- lib/phase/kit/ipa/enterprise_deployment.rb
|
227
|
+
- lib/phase/kit/keys/key.rb
|
228
|
+
- lib/phase/kit/ssh/backend.rb
|
229
|
+
- lib/phase/kit/ssh/bastion_coordinator.rb
|
230
|
+
- lib/phase/kit/ssh/command.rb
|
231
|
+
- lib/phase/kit/ssh/runners.rb
|
232
|
+
- lib/phase/tasks/deploy.rb
|
233
|
+
- lib/phase/util/console.rb
|
211
234
|
- lib/phase/version.rb
|
212
235
|
- phase.gemspec
|
213
236
|
- spec/dsl_spec.rb
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Phase
|
2
|
-
module CLI
|
3
|
-
module Mixins
|
4
|
-
module Loggable
|
5
|
-
|
6
|
-
def log(str)
|
7
|
-
out = "[phase]".green
|
8
|
-
out << " #{ str }"
|
9
|
-
puts out
|
10
|
-
end
|
11
|
-
|
12
|
-
def fail!(str)
|
13
|
-
out = "[phase]".red
|
14
|
-
out << " #{ str }"
|
15
|
-
abort out
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
File without changes
|
File without changes
|