harpoon 0.0.3 → 0.0.4
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/README.md +4 -2
- data/harpoon.gemspec +2 -1
- data/lib/harpoon.rb +4 -2
- data/lib/harpoon/config.rb +48 -5
- data/lib/harpoon/logger.rb +50 -0
- data/lib/harpoon/runner.rb +14 -21
- data/lib/harpoon/service.rb +58 -0
- data/lib/harpoon/services/s3.rb +117 -97
- data/lib/harpoon/services/test.rb +3 -2
- data/lib/harpoon/templates/harpoon.json +2 -4
- data/test/test_config.rb +49 -0
- data/test/test_directory/nested_files/harpoon.json +3 -5
- data/test/test_directory/test_client/harpoon.json +3 -5
- data/test/test_directory/unnested_files/harpoon.json +3 -5
- data/test/test_hosting.rb +2 -2
- data/test/test_runner.rb +4 -8
- data/test/test_service_base.rb +58 -0
- data/test/test_services.rb +1 -0
- metadata +32 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 67816218225a067d3f8b66c9673338f17204fd3d
|
4
|
+
data.tar.gz: 75ba5ab88e915ad54987f0518a620ba4bbeb19b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f61d684a67f40527bc41396e63d3a5115ae2d02ca4677b315890b33cb6202f90ea50bfab68022ab3bd6895ed51ab86fd3574a8a52c3d4fff8005b3c2158e6f3
|
7
|
+
data.tar.gz: d1af82d8c71e92dcdabf854a91cdbc451413dc169f2e4498dfb59f0418aeb768e30fde4bcc560b3ec25f082060b1daac800d4e92f393219afea5e9c65be5a302
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# Harpoon
|
2
|
-
A static site deployer
|
2
|
+
A static site deployer, initially targeting Amazon s3
|
3
3
|
|
4
|
-
This is a work in progress,
|
4
|
+
This project is a work in progress, but it's already pretty functional and serves my purposes well. Feedback/issues/encouragement/complaints are all awesome and appreciated!
|
5
|
+
|
6
|
+
Learn more at [www.getharpoon.com](http://www.getharpoon.com).
|
data/harpoon.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'harpoon'
|
3
|
-
s.version = '0.0.
|
3
|
+
s.version = '0.0.4'
|
4
4
|
s.date = '2014-08-20'
|
5
5
|
s.summary = "A single page app deployer for amazon s3"
|
6
6
|
s.description = "Deploy small server-less webapps to amazon s3, including buckets, dns and permissions"
|
@@ -14,5 +14,6 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.add_dependency "aws-sdk", "~> 1.51.0"
|
15
15
|
s.add_dependency "public_suffix", "~> 1.4.5"
|
16
16
|
s.add_dependency 'colorize', '~> 0.7.3'
|
17
|
+
s.add_dependency 'activesupport', "~> 4.1.5"
|
17
18
|
s.homepage = 'http://www.getharpoon.com'
|
18
19
|
end
|
data/lib/harpoon.rb
CHANGED
@@ -4,8 +4,10 @@ module Harpoon
|
|
4
4
|
require_relative "harpoon/errors"
|
5
5
|
require_relative "harpoon/config"
|
6
6
|
require_relative "harpoon/runner"
|
7
|
+
require_relative "harpoon/logger"
|
7
8
|
|
8
9
|
# Services
|
9
|
-
require_relative "harpoon/
|
10
|
-
require_relative "harpoon/services/
|
10
|
+
require_relative "harpoon/service"
|
11
|
+
require_relative "harpoon/services/test"
|
12
|
+
require_relative "harpoon/services/s3"
|
11
13
|
end
|
data/lib/harpoon/config.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "json"
|
2
2
|
require "fileutils"
|
3
|
+
require "active_support/core_ext/hash"
|
3
4
|
|
4
5
|
module Harpoon
|
5
6
|
class Config
|
@@ -43,18 +44,60 @@ module Harpoon
|
|
43
44
|
end
|
44
45
|
end
|
45
46
|
|
46
|
-
attr_reader :files
|
47
|
-
|
48
47
|
# Initialize a new config object with the data loaded
|
49
48
|
def initialize(data = {}, logger = nil)
|
50
|
-
@
|
49
|
+
@data = data.symbolize_keys
|
51
50
|
@logger = logger
|
52
|
-
@
|
51
|
+
@required_values = []
|
52
|
+
end
|
53
|
+
|
54
|
+
def files
|
55
|
+
Dir.glob(File.join(@data[:directory], "**", "*")).select {|f| !File.directory?(f) && File.basename(f) != "harpoon.json"} if @data[:directory]
|
56
|
+
end
|
57
|
+
|
58
|
+
def deep_merge!(h1)
|
59
|
+
@data.deep_merge! h1.symbolize_keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def requires(required_value)
|
63
|
+
@required_values.push(required_value.to_sym)
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate!
|
67
|
+
@required_values.each do |r|
|
68
|
+
raise Harpoon::Errors::InvalidConfiguration, "Missing #{r} configuration" if @data[r] == nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate
|
73
|
+
begin
|
74
|
+
validate!
|
75
|
+
rescue Harpoon::Errors::InvalidConfiguration
|
76
|
+
return false
|
77
|
+
else
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def data
|
83
|
+
@data.dup
|
84
|
+
end
|
85
|
+
|
86
|
+
def dup
|
87
|
+
c = Harpoon::Config.new(self.data)
|
88
|
+
req = @required_values.dup
|
89
|
+
c.instance_eval {@required_values = req}
|
90
|
+
c
|
53
91
|
end
|
54
92
|
|
55
93
|
# Check for the configuration item
|
56
94
|
def method_missing(method, *args)
|
57
|
-
|
95
|
+
method = method.to_s
|
96
|
+
if method.end_with? "="
|
97
|
+
@data[method.gsub(/=$/, "").to_sym] = args.first
|
98
|
+
else
|
99
|
+
@data[method.to_sym]
|
100
|
+
end
|
58
101
|
end
|
59
102
|
end
|
60
103
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "colorize"
|
3
|
+
|
4
|
+
module Harpoon
|
5
|
+
class Logger < Logger
|
6
|
+
SEVS = %w(DEBUG INFO WARN ERROR FATAL SUGGEST PASS FAIL)
|
7
|
+
CHECKMARK = "\u2713"
|
8
|
+
ARROW = "\u279C"
|
9
|
+
|
10
|
+
def format_severity(severity)
|
11
|
+
SEVS[severity] || 'ANY'
|
12
|
+
end
|
13
|
+
|
14
|
+
def suggest(message, progname = nil, &block)
|
15
|
+
add(5, message, progname, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def pass(message, progname = nil, &block)
|
19
|
+
add(6, message, progname, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fail(message, progname = nil, &block)
|
23
|
+
add(7, message, progname, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def format_message(severity, datetime, progname, msg)
|
27
|
+
case severity.to_s.downcase
|
28
|
+
when "debug"
|
29
|
+
"DEBUG: #{msg}\n"
|
30
|
+
when "info"
|
31
|
+
"#{ARROW} #{msg}\n".colorize(:gray)
|
32
|
+
when "warn"
|
33
|
+
"#{msg}\n".colorize(:yellow)
|
34
|
+
when "error"
|
35
|
+
"#{msg}\n".colorize(:orange)
|
36
|
+
when "fatal"
|
37
|
+
"#{msg}\n".colorize(:red)
|
38
|
+
when "pass"
|
39
|
+
"#{CHECKMARK} - #{msg}\n".colorize(:green)
|
40
|
+
when "fail"
|
41
|
+
"X - #{msg}\n".colorize(:red)
|
42
|
+
when "suggest"
|
43
|
+
"! - #{msg}\n".colorize(:blue)
|
44
|
+
else
|
45
|
+
"#{severity} - #{msg}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/harpoon/runner.rb
CHANGED
@@ -7,11 +7,23 @@ module Harpoon
|
|
7
7
|
@logger = load_logger(options[:log_level])
|
8
8
|
@config = Harpoon::Config.read(options[:config] || Dir.pwd, @logger)
|
9
9
|
@auth = load_auth
|
10
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
def doctor
|
13
|
+
#we specify doctor specifically to test the config
|
14
|
+
begin
|
15
|
+
@service = load_host
|
16
|
+
rescue Harpoon::Errors::InvalidConfiguration => e
|
17
|
+
@logger.fail("Invalid configuration - #{e.message}")
|
18
|
+
else
|
19
|
+
@logger.pass("Valid configuration")
|
20
|
+
@service.doctor
|
21
|
+
end
|
11
22
|
end
|
12
23
|
|
13
24
|
def method_missing(method, *args)
|
14
25
|
#don't know about this here, must be for the service
|
26
|
+
@service = load_host
|
15
27
|
@service.send(method, *args)
|
16
28
|
end
|
17
29
|
|
@@ -39,7 +51,7 @@ module Harpoon
|
|
39
51
|
|
40
52
|
def load_logger(log_level = "info")
|
41
53
|
log_level ||= "info"
|
42
|
-
logger = Logger.new(STDOUT)
|
54
|
+
logger = Harpoon::Logger.new(STDOUT)
|
43
55
|
|
44
56
|
#set log level
|
45
57
|
case log_level.downcase
|
@@ -54,25 +66,6 @@ module Harpoon
|
|
54
66
|
when "fatal"
|
55
67
|
logger.level = Logger::FATAL
|
56
68
|
end
|
57
|
-
|
58
|
-
#set log formatter
|
59
|
-
logger.formatter = proc do |severity, datetime, progname, msg|
|
60
|
-
case severity.to_s.downcase
|
61
|
-
when "debug"
|
62
|
-
"DEBUG: #{msg}\n".colorize(:light_blue)
|
63
|
-
when "info"
|
64
|
-
"#{msg}\n".colorize(:gray)
|
65
|
-
when "warn"
|
66
|
-
"#{msg}\n".colorize(:yellow)
|
67
|
-
when "error"
|
68
|
-
"#{msg}\n".colorize(:orange)
|
69
|
-
when "fatal"
|
70
|
-
"#{msg}\n".colorize(:red)
|
71
|
-
else
|
72
|
-
"#{severity} - #{msg}\n"
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
69
|
logger
|
77
70
|
end
|
78
71
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Harpoon
|
2
|
+
# All services should include this module to work with Harpoon
|
3
|
+
module Service
|
4
|
+
|
5
|
+
#include all the class methods we need
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def auth(key, help = nil)
|
13
|
+
required_auth.push({identifier: key, help: help})
|
14
|
+
end
|
15
|
+
|
16
|
+
def option(key, *args)
|
17
|
+
args = args.last.is_a?(Hash) ? args.pop : {}
|
18
|
+
|
19
|
+
default_options.send("#{key}=", args[:default]) if args[:default]
|
20
|
+
default_options.requires(key) if args[:required]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Defaults setup by the service
|
24
|
+
# These are overridden by the hosting_options in the configuration
|
25
|
+
# file if they exist
|
26
|
+
def default_options
|
27
|
+
@default_options ||= Harpoon::Config.new
|
28
|
+
end
|
29
|
+
|
30
|
+
# An array of required authentication params
|
31
|
+
# These are provided to services as @auth[:key] values
|
32
|
+
def required_auth
|
33
|
+
@required_auth ||= []
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#instance methods
|
38
|
+
def initialize(config, auth, logger)
|
39
|
+
@logger = logger
|
40
|
+
@auth = {}
|
41
|
+
@options = self.class.default_options.dup
|
42
|
+
|
43
|
+
@options.deep_merge!(config.hosting_options || {})
|
44
|
+
#copy over the directory as well
|
45
|
+
@options.directory = config.directory
|
46
|
+
|
47
|
+
@options.validate!
|
48
|
+
|
49
|
+
if self.class.required_auth.length > 0
|
50
|
+
req = self.class.required_auth.map {|h| h[:help]}
|
51
|
+
auth_results = auth.__send__(:get_or_ask, config.hosting, *req)
|
52
|
+
self.class.required_auth.each_with_index do |r, index|
|
53
|
+
@auth[r[:identifier]] = auth_results[index]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/harpoon/services/s3.rb
CHANGED
@@ -6,96 +6,78 @@ require "pathname"
|
|
6
6
|
module Harpoon
|
7
7
|
module Services
|
8
8
|
class S3
|
9
|
-
|
10
|
-
# Store what we're given for later
|
11
|
-
@config = config
|
12
|
-
@auth = auth
|
13
|
-
@logger = logger
|
9
|
+
include Harpoon::Service
|
14
10
|
|
15
|
-
|
16
|
-
|
11
|
+
auth :key, "Key"
|
12
|
+
auth :secret, "Secret"
|
17
13
|
|
18
|
-
|
19
|
-
region = config.hosting_options["region"]
|
20
|
-
else
|
21
|
-
region = "us-west-2"
|
22
|
-
end
|
23
|
-
|
24
|
-
AWS.config(access_key_id: @credentials[0], secret_access_key: @credentials[1], region: region)
|
25
|
-
|
26
|
-
# setup amazon interfaces
|
27
|
-
@s3 = AWS::S3.new
|
28
|
-
@r53 = AWS::Route53.new
|
29
|
-
end
|
14
|
+
option :region, default: "us-west-2"
|
30
15
|
|
31
16
|
def setup
|
32
|
-
if @
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
bucket.website_configuration = cw
|
70
|
-
setup_dns_alias(f, bucket)
|
71
|
-
end
|
17
|
+
if @options.primary_domain
|
18
|
+
#primary domain detected, let's make sure it exists
|
19
|
+
bucket = setup_bucket(@options.primary_domain)
|
20
|
+
#setup bucket to server webpages
|
21
|
+
@logger.info "Setting primary domain as website"
|
22
|
+
bucket.configure_website
|
23
|
+
#setup ACL
|
24
|
+
@logger.info "Setting bucket policy"
|
25
|
+
policy = AWS::S3::Policy.new
|
26
|
+
policy.allow(
|
27
|
+
actions: ['s3:GetObject'],
|
28
|
+
resources: [bucket.objects],
|
29
|
+
principals: :any
|
30
|
+
)
|
31
|
+
|
32
|
+
@logger.debug policy.to_json
|
33
|
+
|
34
|
+
bucket.policy = policy
|
35
|
+
|
36
|
+
|
37
|
+
history = setup_bucket(rollback_bucket(@options.primary_domain))
|
38
|
+
@logger.info "Created rollback bucket"
|
39
|
+
|
40
|
+
setup_dns_alias(@options.primary_domain, bucket)
|
41
|
+
|
42
|
+
|
43
|
+
if @options.forwarded_domain
|
44
|
+
#we also want to forward some domains
|
45
|
+
#make sure we have an array
|
46
|
+
#TODO : Move all of this nonsense to the config object, it should be validating this stuff
|
47
|
+
forwarded = @options.forwarded_domain.is_a?(Array) ? @options.forwarded_domain : [@options.forwarded_domain]
|
48
|
+
forwarded.each do |f|
|
49
|
+
bucket = setup_bucket(f)
|
50
|
+
@logger.info "Seting up redirect to primary"
|
51
|
+
cw = AWS::S3::WebsiteConfiguration.new({redirect_all_requests_to: {host_name: @options.primary_domain}})
|
52
|
+
bucket.website_configuration = cw
|
53
|
+
setup_dns_alias(f, bucket)
|
72
54
|
end
|
73
|
-
|
74
|
-
# print out DNS settings
|
75
|
-
print_dns_settings(@config.domain["primary"])
|
76
55
|
end
|
56
|
+
|
57
|
+
# print out DNS settings
|
58
|
+
print_dns_settings(@options.primary_domain)
|
77
59
|
end
|
78
60
|
end
|
79
61
|
|
80
62
|
def deploy
|
81
|
-
raise Harpoon::Errors::InvalidConfiguration, "Missing list of files" unless @
|
63
|
+
raise Harpoon::Errors::InvalidConfiguration, "Missing list of files" unless @options.files && @options.directory && @options.primary_domain
|
82
64
|
move_existing_to_history!
|
83
|
-
current_bucket =
|
84
|
-
raise Harpoon::Errors::MissingSetup, "Required s3 buckets are not created
|
85
|
-
@logger.info "
|
86
|
-
@
|
65
|
+
current_bucket = s3.buckets[@options.primary_domain]
|
66
|
+
raise Harpoon::Errors::MissingSetup, "Required s3 buckets are not created. Run harpoon doctor to test." unless current_bucket.exists?
|
67
|
+
@logger.info "Copying new release to s3"
|
68
|
+
@options.files.each do |f|
|
87
69
|
@logger.debug "Path: #{f}"
|
88
|
-
relative_path = Pathname.new(f).relative_path_from(Pathname.new(@
|
70
|
+
relative_path = Pathname.new(f).relative_path_from(Pathname.new(@options.directory)).to_s
|
89
71
|
@logger.debug "s3 key: #{relative_path}"
|
90
72
|
current_bucket.objects[relative_path].write(Pathname.new(f))
|
91
73
|
end
|
92
|
-
@logger.info "
|
74
|
+
@logger.info "...done"
|
93
75
|
end
|
94
76
|
|
95
77
|
def list
|
96
78
|
@logger.info "The following rollbacks are available:"
|
97
|
-
if @
|
98
|
-
tree =
|
79
|
+
if @options.primary_domain
|
80
|
+
tree = s3.buckets[rollback_bucket(@options.primary_domain)].as_tree
|
99
81
|
rollbacks = tree.children.collect {|i| i.prefix.gsub(/\/$/, "").to_i }
|
100
82
|
rollbacks.sort!.reverse!
|
101
83
|
rollbacks.each_with_index do |r, index|
|
@@ -105,39 +87,77 @@ module Harpoon
|
|
105
87
|
end
|
106
88
|
|
107
89
|
def doctor
|
90
|
+
@logger.pass "Region: #{@options.region}"
|
108
91
|
# check configuration
|
109
|
-
if @
|
110
|
-
|
111
|
-
@logger.info "Primary Domain: #{@config.domain["primary"]}"
|
112
|
-
else
|
113
|
-
@logger.fatal "Missing Primary Domain"
|
114
|
-
exit
|
115
|
-
end
|
92
|
+
if @options.primary_domain
|
93
|
+
@logger.pass "Primary Domain: #{@options.primary_domain}"
|
116
94
|
else
|
117
|
-
@logger.
|
95
|
+
@logger.fail "Missing Primary Domain"
|
118
96
|
exit
|
119
97
|
end
|
98
|
+
|
99
|
+
if @options.forwarded_domain
|
100
|
+
@logger.pass "Forwarded Domains: #{@options.forwarded_domain}"
|
101
|
+
else
|
102
|
+
@logger.pass "No forwarded domains"
|
103
|
+
end
|
104
|
+
|
120
105
|
# check IAM permissions
|
121
|
-
|
122
|
-
|
106
|
+
|
107
|
+
# check buckets exist and permissions
|
108
|
+
primary_bucket = s3.buckets[@options.primary_domain]
|
123
109
|
if primary_bucket.exists?
|
124
|
-
@logger.
|
110
|
+
@logger.pass "Primary bucket exists"
|
125
111
|
else
|
126
|
-
@logger.
|
112
|
+
@logger.fail "Missing Primary domain bucket"
|
127
113
|
end
|
114
|
+
|
115
|
+
if @options.forwarded_domain
|
116
|
+
forwarded = @options.forwarded_domain.is_a?(Array) ? @options.forwarded_domain : [@options.forwarded_domain]
|
117
|
+
forwarded.each do |f|
|
118
|
+
bucket = s3.buckets[f]
|
119
|
+
if bucket.exists?
|
120
|
+
@logger.pass "Forwarded bucket exists: #{f}"
|
121
|
+
#check forwarding
|
122
|
+
wc = bucket.website_configuration.to_hash
|
123
|
+
@logger.debug "Website Configuration: #{wc}"
|
124
|
+
if wc && wc[:redirect_all_requests_to] && wc[:redirect_all_requests_to][:host_name] == @options.primary_domain
|
125
|
+
@logger.pass "Forwarding setup: #{f}"
|
126
|
+
else
|
127
|
+
@logger.fail "Forwarding needs to be setup: #{f}"
|
128
|
+
@logger.suggest "Run `harpoon setup`"
|
129
|
+
end
|
130
|
+
else
|
131
|
+
@logger.fail "Forwarded bucket doesn't exist: #{f}"
|
132
|
+
@logger.suggest "Run `harpoon setup`"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
128
137
|
# check domain setup
|
138
|
+
|
139
|
+
|
129
140
|
# print DNS settings
|
130
|
-
print_dns_settings(@
|
141
|
+
print_dns_settings(@options.primary_domain)
|
142
|
+
@logger.info "...done"
|
131
143
|
end
|
132
144
|
|
133
145
|
def rollback
|
134
146
|
@logger.info "Not yet implemented!"
|
135
|
-
@logger.info "
|
147
|
+
@logger.info "Your rollbacks ARE safe on s3 #{rollback_bucket(@options.primary_domain)}"
|
136
148
|
self.list
|
137
149
|
end
|
138
150
|
|
139
151
|
private
|
140
152
|
|
153
|
+
def s3
|
154
|
+
@s3 ||= AWS::S3.new({access_key_id: @auth[:key], secret_access_key: @auth[:secret], region: @options.region})
|
155
|
+
end
|
156
|
+
|
157
|
+
def r53
|
158
|
+
@r53 ||= AWS::Route53.new({access_key_id: @auth[:key], secret_access_key: @auth[:secret], region: @options.region})
|
159
|
+
end
|
160
|
+
|
141
161
|
def setup_dns_alias(domain, bucket)
|
142
162
|
@logger.debug "Setup Domain Alias for #{domain}"
|
143
163
|
#extract root domain
|
@@ -151,8 +171,8 @@ module Harpoon
|
|
151
171
|
|
152
172
|
|
153
173
|
#ensure we have a hosted zone
|
154
|
-
hosted_zone =
|
155
|
-
hosted_zone =
|
174
|
+
hosted_zone = r53.hosted_zones.find {|h| h.name == rdomain}
|
175
|
+
hosted_zone = r53.hosted_zones.create(rdomain, {comment: "Created By Harpoon"}) if !hosted_zone
|
156
176
|
|
157
177
|
record = hosted_zone.rrsets[domain, "A"]
|
158
178
|
|
@@ -165,9 +185,9 @@ module Harpoon
|
|
165
185
|
|
166
186
|
def setup_bucket(bucket_name)
|
167
187
|
@logger.info "Creating bucket: #{bucket_name}"
|
168
|
-
bucket =
|
188
|
+
bucket = s3.buckets[bucket_name]
|
169
189
|
unless bucket.exists?
|
170
|
-
bucket =
|
190
|
+
bucket = s3.buckets.create(bucket_name)
|
171
191
|
end
|
172
192
|
bucket
|
173
193
|
end
|
@@ -206,13 +226,13 @@ module Harpoon
|
|
206
226
|
@logger.debug "Print DNS Settings for: #{domain}"
|
207
227
|
rdomain = "#{root_domain(domain)}."
|
208
228
|
@logger.debug "Root Domain: #{rdomain}"
|
209
|
-
@logger.
|
210
|
-
@logger.
|
211
|
-
hosted_zone =
|
229
|
+
@logger.suggest "=============================="
|
230
|
+
@logger.suggest "Please transfer domain to Amazon Nameservers:"
|
231
|
+
hosted_zone = r53.hosted_zones.find {|h| h.name == rdomain}
|
212
232
|
hosted_zone.rrsets[rdomain, "NS"].resource_records.each do |r|
|
213
|
-
@logger.
|
233
|
+
@logger.suggest r[:value]
|
214
234
|
end
|
215
|
-
@logger.
|
235
|
+
@logger.suggest "=============================="
|
216
236
|
end
|
217
237
|
|
218
238
|
def rollback_bucket(domain)
|
@@ -220,10 +240,10 @@ module Harpoon
|
|
220
240
|
end
|
221
241
|
|
222
242
|
def move_existing_to_history!
|
223
|
-
raise Harpoon::Errors::InvalidConfiguration, "Must have a primary domain defined" unless @
|
243
|
+
raise Harpoon::Errors::InvalidConfiguration, "Must have a primary domain defined" unless @options.primary_domain
|
224
244
|
@logger.info "Moving existing deploy to history"
|
225
|
-
current =
|
226
|
-
history =
|
245
|
+
current = s3.buckets[@options.primary_domain]
|
246
|
+
history = s3.buckets[rollback_bucket(@options.primary_domain)]
|
227
247
|
raise Harpoon::Errors::MissingSetup, "The expected buckets are not yet created, please try running harpoon setup" unless current.exists? && history.exists?
|
228
248
|
|
229
249
|
current_date = Time.now.to_i
|
@@ -1,16 +1,17 @@
|
|
1
1
|
module Harpoon
|
2
2
|
module Services
|
3
3
|
class Test
|
4
|
+
include Harpoon::Service
|
4
5
|
attr_accessor :config, :requests
|
5
6
|
|
6
7
|
def initialize(config = nil, auth = nil, logger = nil)
|
7
|
-
@auth = auth
|
8
8
|
@config = config
|
9
|
-
@
|
9
|
+
@auth = auth
|
10
10
|
@logger = logger
|
11
11
|
end
|
12
12
|
|
13
13
|
def method_missing(method, *args)
|
14
|
+
@requests ||= []
|
14
15
|
@requests.push [method, args]
|
15
16
|
end
|
16
17
|
end
|
@@ -3,11 +3,9 @@
|
|
3
3
|
"hosting" : "s3",
|
4
4
|
"auth_namespace" : "main",
|
5
5
|
"hosting_options" : {
|
6
|
+
"primary_domain": "www.domain.com",
|
7
|
+
"forwarded_domain" : ["domain.com"],
|
6
8
|
"region" : "us-west-2"
|
7
9
|
},
|
8
|
-
"domain" : {
|
9
|
-
"primary" : "www.domain.com",
|
10
|
-
"forwarded" : ["domain.com"]
|
11
|
-
},
|
12
10
|
"directory" : ""
|
13
11
|
}
|
data/test/test_config.rb
CHANGED
@@ -57,4 +57,53 @@ describe "Config File" do
|
|
57
57
|
assert_equal 2, unnested.files.length, "Should have found 2 files"
|
58
58
|
assert_equal [File.join(Dir.pwd, "test", "test_directory", "unnested_files", "nested", "test2.txt"), File.join(Dir.pwd, "test", "test_directory", "unnested_files", "test.txt")], unnested.files, "Should have found the right files"
|
59
59
|
end
|
60
|
+
|
61
|
+
it "Should provide some deep merge functionality" do
|
62
|
+
h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
|
63
|
+
h2 = { x: { y: [7,8,9] }, z: "xyz" }
|
64
|
+
|
65
|
+
c1 = Harpoon::Config.new(h1.dup)
|
66
|
+
c2 = Harpoon::Config.new(h2.dup)
|
67
|
+
|
68
|
+
assert_equal ({ x: { y: [7, 8, 9] }, z: "xyz" }), c1.deep_merge!(h2)
|
69
|
+
assert_equal ({ x: { y: [4, 5, 6] }, z: [7, 8, 9] }), c2.deep_merge!(h1)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "Should let you get to all the data" do
|
73
|
+
d = {key: "value", key2: "value2"}
|
74
|
+
c1 = Harpoon::Config.new(d)
|
75
|
+
assert_equal d, c1.data, "Should have returned data"
|
76
|
+
end
|
77
|
+
|
78
|
+
it "Should let you require data and then check to confirm you have it" do
|
79
|
+
c1 = Harpoon::Config.new
|
80
|
+
c1.requires :required_1
|
81
|
+
assert_raises Harpoon::Errors::InvalidConfiguration do
|
82
|
+
c1.validate!
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_equal false, c1.validate, "Should have a silent validation as well"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "Should let you set values" do
|
89
|
+
c1 = Harpoon::Config.new
|
90
|
+
c1.test_value = true
|
91
|
+
assert_equal true, c1.test_value, "Should have set the value"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "shouldn't care about strings and symbols" do
|
95
|
+
c1 = Harpoon::Config.new({hosting_options: true})
|
96
|
+
c1.requires :hosting_options
|
97
|
+
assert_equal true, c1.validate, "Should have caught it"
|
98
|
+
|
99
|
+
assert_equal true, c1.hosting_options, "Should have found it!"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "Should have a working dup method" do
|
103
|
+
c1 = Harpoon::Config.new({hosting: true})
|
104
|
+
c2 = c1.dup
|
105
|
+
c1.new_value = true
|
106
|
+
assert !c2.new_value, "Shouldn't have a new value in c2"
|
107
|
+
end
|
108
|
+
|
60
109
|
end
|
@@ -3,11 +3,9 @@
|
|
3
3
|
"hosting" : "s3",
|
4
4
|
"auth_namespace" : "main",
|
5
5
|
"hosting_options" : {
|
6
|
-
"region" : "US Standard"
|
7
|
-
|
8
|
-
|
9
|
-
"primary" : "www.domain.com",
|
10
|
-
"forwarded" : ["domain.com"]
|
6
|
+
"region" : "US Standard",
|
7
|
+
"primary_domain" : "www.domain.com",
|
8
|
+
"forwarded_domain" : ["domain.com"]
|
11
9
|
},
|
12
10
|
"directory" : "nested/directory"
|
13
11
|
}
|
@@ -3,11 +3,9 @@
|
|
3
3
|
"hosting" : "test",
|
4
4
|
"auth_namespace" : "test-namespace",
|
5
5
|
"hosting_options" : {
|
6
|
-
"option" : "value"
|
7
|
-
|
8
|
-
|
9
|
-
"primary" : "www.domain.com",
|
10
|
-
"forwarded" : ["domain.com"]
|
6
|
+
"option" : "value",
|
7
|
+
"primary_domain" : "www.domain.com",
|
8
|
+
"forwarded_domain" : ["domain.com"]
|
11
9
|
},
|
12
10
|
"directory" : ""
|
13
11
|
}
|
@@ -3,10 +3,8 @@
|
|
3
3
|
"hosting" : "s3",
|
4
4
|
"auth_namespace" : "main",
|
5
5
|
"hosting_options" : {
|
6
|
-
"region" : "
|
7
|
-
|
8
|
-
|
9
|
-
"primary" : "www.domain.com",
|
10
|
-
"forwarded" : ["domain.com"]
|
6
|
+
"region" : "us-west-2",
|
7
|
+
"primary_domain" : "www.domain.com",
|
8
|
+
"forwarded_domain" : ["domain.com"]
|
11
9
|
}
|
12
10
|
}
|
data/test/test_hosting.rb
CHANGED
@@ -2,7 +2,7 @@ require "helper"
|
|
2
2
|
|
3
3
|
describe "Test Hosting Module" do
|
4
4
|
before do
|
5
|
-
@hosting = Harpoon::Services::Test.new
|
5
|
+
@hosting = Harpoon::Services::Test.new(Object.new, Object.new, Object.new)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "Should let me make requests" do
|
@@ -10,5 +10,5 @@ describe "Test Hosting Module" do
|
|
10
10
|
assert_equal :deploy, @hosting.requests[0][0]
|
11
11
|
assert_equal ["options", "go", "here"], @hosting.requests[0][1]
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
end
|
data/test/test_runner.rb
CHANGED
@@ -2,6 +2,7 @@ require "helper"
|
|
2
2
|
describe "Runner" do
|
3
3
|
before do
|
4
4
|
@runner = Harpoon::Runner.new({config: "test/test_directory/test_client"})
|
5
|
+
@runner.instance_eval {@service = load_host}
|
5
6
|
end
|
6
7
|
|
7
8
|
it "Should load the config from the config option" do
|
@@ -16,19 +17,14 @@ describe "Runner" do
|
|
16
17
|
assert_equal "test-namespace", @runner.instance_eval {@auth.namespace}, "Should have set the namespace correctly"
|
17
18
|
end
|
18
19
|
|
19
|
-
it "Should be passing in the configuration and auth to the service" do
|
20
|
-
assert_equal @runner.instance_eval {@auth}, @runner.instance_eval {@service.instance_eval {@auth}}, "Should have gotten the right auth"
|
21
|
-
assert_equal @runner.instance_eval {@config}, @runner.instance_eval {@service.instance_eval {@config}}, "Should have gotten the right config"
|
22
|
-
end
|
23
|
-
|
24
20
|
it "Should pass any unknown commands to the service" do
|
25
21
|
@runner.deploy
|
26
22
|
assert_equal :deploy, @runner.instance_eval {@service.requests[0][0]}, "Should have run deploy on the service"
|
27
23
|
end
|
28
24
|
|
29
25
|
it "Should pass a default logger to everyone" do
|
30
|
-
assert_equal Logger, @runner.instance_eval {@service.instance_eval {@logger.class}}, "Should have passed a logger to the service"
|
31
|
-
assert_equal Logger, @runner.instance_eval {@auth.instance_eval {@logger.class}}, "Should have passed a logger to the auth"
|
32
|
-
assert_equal Logger, @runner.instance_eval {@config.instance_eval {@logger.class}}, "Should have passed a logger to the config"
|
26
|
+
assert_equal Harpoon::Logger, @runner.instance_eval {@service.instance_eval {@logger.class}}, "Should have passed a logger to the service"
|
27
|
+
assert_equal Harpoon::Logger, @runner.instance_eval {@auth.instance_eval {@logger.class}}, "Should have passed a logger to the auth"
|
28
|
+
assert_equal Harpoon::Logger, @runner.instance_eval {@config.instance_eval {@logger.class}}, "Should have passed a logger to the config"
|
33
29
|
end
|
34
30
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
#overwrite test service for this test
|
4
|
+
module Harpoon
|
5
|
+
module Services
|
6
|
+
class TestBase
|
7
|
+
include Harpoon::Service
|
8
|
+
attr_accessor :config, :requests
|
9
|
+
|
10
|
+
auth :auth1, "Auth1"
|
11
|
+
auth :auth2, "Auth2"
|
12
|
+
|
13
|
+
option :default_option, default: true
|
14
|
+
option :default_option_2, default: true
|
15
|
+
option :default_option_required, required: true
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
@requests ||= []
|
19
|
+
@requests.push [method, args]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "Service Base" do
|
26
|
+
before do
|
27
|
+
@mock_auth = MiniTest::Mock.new
|
28
|
+
@mock_logger = MiniTest::Mock.new
|
29
|
+
d = {
|
30
|
+
hosting: :test,
|
31
|
+
hosting_options: {default_option: "one", new_option: true, default_option_required: true}
|
32
|
+
}
|
33
|
+
@mock_options = Harpoon::Config.new(d)
|
34
|
+
|
35
|
+
@mock_auth.expect :get_or_ask, ["asdf", "asdf2"], [Symbol, String, String]
|
36
|
+
end
|
37
|
+
|
38
|
+
it "Should request and verify authentication" do
|
39
|
+
@service = Harpoon::Services::TestBase.new(@mock_options, @mock_auth, @mock_logger)
|
40
|
+
@mock_auth.verify
|
41
|
+
assert_equal "asdf", @service.instance_eval {@auth[:auth1]}, "Should have stored auth1 correctly"
|
42
|
+
assert_equal "asdf2", @service.instance_eval {@auth[:auth2]}, "Should have stored auth2 correctly"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "Should have merged in the hosting options with default values" do
|
46
|
+
@service = Harpoon::Services::TestBase.new(@mock_options, @mock_auth, @mock_logger)
|
47
|
+
assert_equal "one", @service.instance_eval {@options.default_option}
|
48
|
+
assert_equal true, @service.instance_eval {@options.default_option_2}
|
49
|
+
assert_equal true, @service.instance_eval {@options.new_option}
|
50
|
+
end
|
51
|
+
|
52
|
+
it "Should raise an error if required options aren't included" do
|
53
|
+
@mock_options = Harpoon::Config.new({hosting: :test})
|
54
|
+
assert_raises Harpoon::Errors::InvalidConfiguration do
|
55
|
+
@service = Harpoon::Services::TestBase.new(@mock_options, @mock_auth, @mock_logger)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/test/test_services.rb
CHANGED
@@ -7,6 +7,7 @@ describe "Test all services" do
|
|
7
7
|
# functions
|
8
8
|
Harpoon::Services.constants.each do |c|
|
9
9
|
next if c.to_s == "Test" #skip the test
|
10
|
+
next if c.to_s == "TestBase" #skip base test
|
10
11
|
m = Kernel.const_get("Harpoon").const_get("Services").const_get(c).instance_methods
|
11
12
|
assert_includes m, :deploy, "Should include a deploy method"
|
12
13
|
assert_includes m, :doctor, "Should include a doctor method"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: harpoon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Quinn
|
@@ -14,72 +14,86 @@ dependencies:
|
|
14
14
|
name: thor
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 0.19.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 0.19.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: netrc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 0.7.7
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.7.7
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: aws-sdk
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 1.51.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 1.51.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: public_suffix
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ~>
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 1.4.5
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - ~>
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.4.5
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: colorize
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - ~>
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: 0.7.3
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - ~>
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 0.7.3
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.1.5
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.1.5
|
83
97
|
description: Deploy small server-less webapps to amazon s3, including buckets, dns
|
84
98
|
and permissions
|
85
99
|
email: ryan@mazondo.com
|
@@ -88,7 +102,7 @@ executables:
|
|
88
102
|
extensions: []
|
89
103
|
extra_rdoc_files: []
|
90
104
|
files:
|
91
|
-
- .gitignore
|
105
|
+
- ".gitignore"
|
92
106
|
- LICENSE.md
|
93
107
|
- README.md
|
94
108
|
- Rakefile
|
@@ -100,7 +114,9 @@ files:
|
|
100
114
|
- lib/harpoon/client.rb
|
101
115
|
- lib/harpoon/config.rb
|
102
116
|
- lib/harpoon/errors.rb
|
117
|
+
- lib/harpoon/logger.rb
|
103
118
|
- lib/harpoon/runner.rb
|
119
|
+
- lib/harpoon/service.rb
|
104
120
|
- lib/harpoon/services/s3.rb
|
105
121
|
- lib/harpoon/services/test.rb
|
106
122
|
- lib/harpoon/templates/harpoon.json
|
@@ -118,6 +134,7 @@ files:
|
|
118
134
|
- test/test_directory/unnested_files/test.txt
|
119
135
|
- test/test_hosting.rb
|
120
136
|
- test/test_runner.rb
|
137
|
+
- test/test_service_base.rb
|
121
138
|
- test/test_services.rb
|
122
139
|
homepage: http://www.getharpoon.com
|
123
140
|
licenses:
|
@@ -129,17 +146,17 @@ require_paths:
|
|
129
146
|
- lib
|
130
147
|
required_ruby_version: !ruby/object:Gem::Requirement
|
131
148
|
requirements:
|
132
|
-
- -
|
149
|
+
- - ">="
|
133
150
|
- !ruby/object:Gem::Version
|
134
151
|
version: '0'
|
135
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
153
|
requirements:
|
137
|
-
- -
|
154
|
+
- - ">="
|
138
155
|
- !ruby/object:Gem::Version
|
139
156
|
version: '0'
|
140
157
|
requirements: []
|
141
158
|
rubyforge_project:
|
142
|
-
rubygems_version: 2.
|
159
|
+
rubygems_version: 2.2.2
|
143
160
|
signing_key:
|
144
161
|
specification_version: 4
|
145
162
|
summary: A single page app deployer for amazon s3
|