titanic 0.0.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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/lib/titanic.rb +218 -0
- data/lib/titanic/version.rb +3 -0
- data/titanic.gemspec +25 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Martin Rhoads
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Titanic
|
2
|
+
|
3
|
+
this gem handles pushing builds, pushing deploys, and consuming them
|
4
|
+
from a client
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'titanic'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install titanic
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
the useful public methods are:
|
23
|
+
|
24
|
+
- put_build(uuid,path)
|
25
|
+
- put_deploy(uuid)
|
26
|
+
- get_deploy_from_s3
|
27
|
+
- get_deploy_from_http
|
28
|
+
- get_build_from_s3(uuid)
|
29
|
+
- get_build_from_http(uuid)
|
30
|
+
|
31
|
+
basic options would look something like:
|
32
|
+
|
33
|
+
opts = {
|
34
|
+
'project' => 'feefee',
|
35
|
+
'environment' => 'some_cool_environment',
|
36
|
+
'role' => 'coolest_server_ever',
|
37
|
+
'access_key' => ENV['AWS_ACCESS_KEY'],
|
38
|
+
'secret_key' => ENV['AWS_SECRET_KEY'],
|
39
|
+
'bucket' => 'sssp',
|
40
|
+
'prefix' => 'titanic',
|
41
|
+
'url' => 'http://ssspy.d.musta.ch',
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
1. Fork it
|
48
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
49
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
50
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
51
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/titanic.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require "titanic/version"
|
2
|
+
require 'logger'
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'net/http'
|
6
|
+
|
7
|
+
module Titanic
|
8
|
+
class Titanic
|
9
|
+
|
10
|
+
def initialize(opts={})
|
11
|
+
@paranoid = true
|
12
|
+
log.debug "opts are #{opts}"
|
13
|
+
require_and_set(opts,[
|
14
|
+
'project',
|
15
|
+
])
|
16
|
+
set(opts,[
|
17
|
+
'access_key',
|
18
|
+
'secret_key',
|
19
|
+
'bucket',
|
20
|
+
'prefix',
|
21
|
+
'url',
|
22
|
+
'environment',
|
23
|
+
'role',
|
24
|
+
'paranoid',
|
25
|
+
])
|
26
|
+
log.debug "paranoid is set to #{@paranoid}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def put_build(uuid,path)
|
30
|
+
build_path = archive_path uuid
|
31
|
+
if s3.objects[build_path].exists? and @paranoid
|
32
|
+
log.error "refusing to overwrite build because paranoid is set"
|
33
|
+
log.error "unset paranoid to remove this and other saftey features"
|
34
|
+
log.error "exiting..."
|
35
|
+
exit 2
|
36
|
+
else
|
37
|
+
log.info "putting #{path} at #{build_path}"
|
38
|
+
s3.objects[build_path].write(Pathname.new(path))
|
39
|
+
log.debug "successfully put #{File.basename path}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def put_deploy(uuid)
|
44
|
+
verify(['environment','role'])
|
45
|
+
log.info "releasing new deploy #{uuid} at #{deploy_path}"
|
46
|
+
s3.objects[deploy_path].write(uuid)
|
47
|
+
log.debug "done"
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_deploy_from_s3()
|
51
|
+
unless s3.objects[deploy_path].exists?
|
52
|
+
log.error "deployment #{deploy_path} does not exist"
|
53
|
+
log.error "exiting"
|
54
|
+
exit 8
|
55
|
+
end
|
56
|
+
return s3.objects[deploy_path].read
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_deploy_from_http()
|
60
|
+
proper_path = deploy_path
|
61
|
+
proper_path = "/#{proper_path}" unless proper_path[0] == '/'
|
62
|
+
begin
|
63
|
+
log.debug "trying to get #{proper_path} from #{@url}"
|
64
|
+
uri = URI File.join(@url,proper_path)
|
65
|
+
return Net::HTTP.get(uri)
|
66
|
+
rescue
|
67
|
+
raise "could not get deploy #{proper_path} from #{@url}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_build_from_s3(uuid,path)
|
72
|
+
build_path = archive_path uuid
|
73
|
+
unless s3.objects[build_path].exists?
|
74
|
+
log.error "tring to get build #{build_path} from #{@bucket} which does not exist"
|
75
|
+
log.error "exiting..."
|
76
|
+
exit 3
|
77
|
+
end
|
78
|
+
log.debug "getting build #{build_path} from #{@bucket}"
|
79
|
+
unless Dir.exists? File.dirname(path)
|
80
|
+
if @paranoid
|
81
|
+
log.error "looks like the parent dir for #{path} does not exist and paranoid is set"
|
82
|
+
log.error "set paranoid to false to automatically create this and do other potentially dangerous things"
|
83
|
+
log.error "exiting..."
|
84
|
+
exit 4
|
85
|
+
else
|
86
|
+
FileUtils.mkdir_p File.dirname(path)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
if File.exists?(path) and @paranoid
|
91
|
+
log.error "you are trying to clober #{path} and paranoid is set"
|
92
|
+
log.error "unset this if you don't care about these types of things"
|
93
|
+
log.error "exiting..."
|
94
|
+
exit 5
|
95
|
+
end
|
96
|
+
|
97
|
+
log.info "downloading s3://#{@bucket}/#{build_path} to #{path} "
|
98
|
+
# streaming download from S3 to a file on disk
|
99
|
+
File.open(path, 'w') do |file|
|
100
|
+
s3.objects[build_path].read do |chunk|
|
101
|
+
file.write(chunk)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_build_from_http(uuid,path)
|
107
|
+
unless Dir.exists? File.dirname(path)
|
108
|
+
if @paranoid
|
109
|
+
log.error "the parent directory of #{path} does not exist and paranoid is set."
|
110
|
+
log.error "consider unsetting paranoid if you want to take a walk on the wild side"
|
111
|
+
log.error "exiting..."
|
112
|
+
exit 6
|
113
|
+
else
|
114
|
+
FileUtils.mkdir_p File.dirname(path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if File.exists?(path) and @paranoid
|
119
|
+
log.error "refusing to clobbler #{path} because paranoid is set"
|
120
|
+
log.error "unset paranoid if your in the danger zone"
|
121
|
+
log.error "exiting..."
|
122
|
+
exit 7
|
123
|
+
end
|
124
|
+
|
125
|
+
build_path = archive_path uuid
|
126
|
+
verify ['url', 'prefix']
|
127
|
+
uri = URI File.join(@url,build_path)
|
128
|
+
log.debug "trying to get @{uri}"
|
129
|
+
begin
|
130
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
131
|
+
request = Net::HTTP::Get.new uri.request_uri
|
132
|
+
http.request request do |response|
|
133
|
+
open path, 'w' do |io|
|
134
|
+
response.read_body do |chunk|
|
135
|
+
io.write chunk
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue
|
141
|
+
raise "could not get #{build_path} from #{@url}"
|
142
|
+
end
|
143
|
+
log.info "downloaded #{uri} to #{path}"
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def archive_path(uuid)
|
149
|
+
File.join @prefix, @project, 'artifacts', uuid
|
150
|
+
end
|
151
|
+
|
152
|
+
def deploy_path
|
153
|
+
File.join @prefix, @project, 'deploys', @environment, @role
|
154
|
+
end
|
155
|
+
|
156
|
+
def s3
|
157
|
+
@s3 ||= configure_s3
|
158
|
+
end
|
159
|
+
|
160
|
+
def configure_s3
|
161
|
+
verify(['access_key', 'secret_key', 'bucket', 'prefix'])
|
162
|
+
AWS.config(
|
163
|
+
:access_key_id => @access_key,
|
164
|
+
:secret_access_key => @secret_key,
|
165
|
+
)
|
166
|
+
bucket = AWS::S3.new.buckets[@bucket]
|
167
|
+
log.debug "checking if bucket #{bucket} exists"
|
168
|
+
unless bucket.exists?
|
169
|
+
log.error "bucket #{bucket} does not exist"
|
170
|
+
if @paranoid
|
171
|
+
log.error "unset paranoid to create bucket and do other non-paranoid type things"
|
172
|
+
log.error "exiting"
|
173
|
+
exit 1
|
174
|
+
else
|
175
|
+
log.error "paranoid is not set, so we will create the bucket"
|
176
|
+
bucket = AWS::S3.new.buckets.create(@bucket)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
return bucket
|
180
|
+
end
|
181
|
+
|
182
|
+
def require_and_set(opts,required_opts)
|
183
|
+
required_opts.each do |required|
|
184
|
+
raise "you need to provide #{required}" unless opts[required]
|
185
|
+
log.debug "setting instance variable #{required} to #{opts[required]}"
|
186
|
+
instance_variable_set "@#{required}", opts[required]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def set(opts,optional_opts)
|
191
|
+
log.debug "in set with opts #{opts}"
|
192
|
+
optional_opts.each do |optional|
|
193
|
+
log.debug "in set with #{optional}"
|
194
|
+
instance_variable_set("@#{optional}", opts[optional]) if opts.include? optional
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def verify(required_opts)
|
199
|
+
required_opts.each do |required|
|
200
|
+
value = instance_variable_get "@#{required}"
|
201
|
+
raise "you need to specify #{required}" unless instance_variable_get "@#{required}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def log
|
206
|
+
@logger ||= configure_logger_for
|
207
|
+
end
|
208
|
+
|
209
|
+
def configure_logger_for
|
210
|
+
logger = Logger.new(STDOUT)
|
211
|
+
logger.info "set the DEBUG environment to get debug messages" unless ENV['DEBUG']
|
212
|
+
logger.level = Logger::INFO unless ENV['DEBUG']
|
213
|
+
logger.progname = self.class.name
|
214
|
+
logger
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
data/titanic.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'titanic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "titanic"
|
8
|
+
spec.version = Titanic::VERSION
|
9
|
+
spec.authors = ["Martin Rhoads"]
|
10
|
+
spec.email = ["martin.rhoads@airbnb.com"]
|
11
|
+
spec.description = %q{deploys, published, and consumes stuff}
|
12
|
+
spec.summary = %q{deploys, published, and consumes stuff}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency "aws-sdk"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: titanic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Martin Rhoads
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: aws-sdk
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: deploys, published, and consumes stuff
|
63
|
+
email:
|
64
|
+
- martin.rhoads@airbnb.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/titanic.rb
|
75
|
+
- lib/titanic/version.rb
|
76
|
+
- titanic.gemspec
|
77
|
+
homepage: ''
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.23
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: deploys, published, and consumes stuff
|
102
|
+
test_files: []
|