phase 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|