deploy_aws 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +67 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/deploy +18 -0
- data/bin/setup +7 -0
- data/deploy_aws.gemspec +30 -0
- data/lib/deploy.rb +207 -0
- data/lib/deploy/eb/application.rb +22 -0
- data/lib/deploy/eb/configuration.rb +26 -0
- data/lib/deploy/eb/platform.rb +41 -0
- data/lib/deploy/eb/state.rb +50 -0
- data/lib/deploy/error_handler.rb +30 -0
- data/lib/deploy/iam/client.rb +23 -0
- data/lib/deploy/repository.rb +69 -0
- data/lib/deploy/s3/configuration.rb +103 -0
- data/lib/deploy/s3/platform.rb +25 -0
- data/lib/deploy/s3/state.rb +59 -0
- data/lib/deploy/version.rb +3 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 116db5a7d2df916d2e15b6d152529785bc0d8e16
|
4
|
+
data.tar.gz: 44bc077b0a44f9ec8a17e86024ebe3af5ccb0595
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cbc4da23d9a26104dd9a8edba823931f5e977e24688f179ab112ceebe914613c266a60db52e1673963dc47c64ee69d9bd4f3eeddeef914861b5de80fc21ad2db
|
7
|
+
data.tar.gz: ff8d71993dfbdc5ac58adde1f76a4c660b1ab35409b99913cc42b744b83d700b59bf99652f8439798d60b98b0d353b968f9fd093e4d71ced343ed8ed46ca266e
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Michael Smirnoff
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Deploy AWS
|
2
|
+
|
3
|
+
Deploy AWS is a gem for deploying your application code to AWS.
|
4
|
+
Currently, covered deployment usage scenarios are:
|
5
|
+
AWS Elastic Beanstalk applications (via AWS EB CLI)
|
6
|
+
AWS S3 hosted web sites (via npm bundle)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
For multiple application usage, install the gem directly:
|
11
|
+
|
12
|
+
```shell
|
13
|
+
gem install deploy_aws
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
```shell
|
19
|
+
deploy_aws
|
20
|
+
```
|
21
|
+
|
22
|
+
## Configuration
|
23
|
+
|
24
|
+
The gem assumes that your shell includes AWS access keys and region configured.
|
25
|
+
The gem makes certain assumptions about your S3 folder hierarchy, if S3 is the
|
26
|
+
deployment target./
|
27
|
+
Specifically, it expects to find a bucket containing folders that map to your deployable applications.
|
28
|
+
The application that is to be deployed is then expected to be found in the same named top level S3 bucket.
|
29
|
+
|
30
|
+
ElasticBeanstalk applications are deployed directly if detected, and this
|
31
|
+
configuration is not required.
|
32
|
+
|
33
|
+
Example: your S3 bucket named `configs-all` contains your application configurations.
|
34
|
+
The applications themselves are in S3 buckets.
|
35
|
+
|
36
|
+
Then, if you have a static hosted S3 site in the bucket `my-site`,
|
37
|
+
you'd expect to have a configuration collection in S3 like this:
|
38
|
+
|
39
|
+
`configs-all/my-site/config/1.0/somesetting.yml`
|
40
|
+
|
41
|
+
`configs-all/my-site/config/2.0/somesetting.yml`
|
42
|
+
|
43
|
+
Then, the deployment process will ask you to choose to deploy `my-site`, and the configuration version to be deployed. It will then attempt to find an S3 bucket with the name `my-site`.
|
44
|
+
|
45
|
+
## Usage
|
46
|
+
|
47
|
+
After installing the gem, execute `deploy_aws` in your deployable project directory.
|
48
|
+
Follow the interactive prompts.
|
49
|
+
As per Configuration section, the deployment code makes certain assumptions.
|
50
|
+
Please consider if they apply to your use case.
|
51
|
+
|
52
|
+
## Development
|
53
|
+
|
54
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
55
|
+
|
56
|
+
Then, run `rake test` or `ruby -Ilib:test test/*` to run the tests. For benchmarked/coloured output, try `ruby -Ilib:test test/* -p`.
|
57
|
+
|
58
|
+
To install this gem onto your local machine, run `rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
59
|
+
|
60
|
+
## Contributing
|
61
|
+
|
62
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sealink/deploy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
63
|
+
|
64
|
+
|
65
|
+
## License
|
66
|
+
|
67
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "deploy"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/deploy
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
|
4
|
+
lib = File.expand_path(File.dirname(THIS_FILE) + '/../lib')
|
5
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
6
|
+
|
7
|
+
require "deploy"
|
8
|
+
|
9
|
+
tag = ARGV[0]
|
10
|
+
# Demand correct invocation, one argument only for tag, clean git, changelog
|
11
|
+
if tag.nil?
|
12
|
+
abort "USAGE:
|
13
|
+
#{File.basename($0)} [TAG]
|
14
|
+
TAG: tag to deploy or create on current commit
|
15
|
+
Incorrect number of arguments given."
|
16
|
+
end
|
17
|
+
deployment = Deploy::Runner.new(tag)
|
18
|
+
deployment.run
|
data/bin/setup
ADDED
data/deploy_aws.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'deploy/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "deploy_aws"
|
9
|
+
spec.version = Deploy::VERSION
|
10
|
+
spec.authors = ["Michael Smirnoff"]
|
11
|
+
spec.email = ["michael.smirnoff@sealink.com.au"]
|
12
|
+
|
13
|
+
spec.summary = %q{Deploys from current Git repo to AWS}
|
14
|
+
spec.description = %q{Deploys from current Git repo to AWS EB or S3 (via node)}
|
15
|
+
spec.homepage = "https://github.com/sealink/deploy_aws"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
|
20
|
+
spec.bindir = "bin"
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "aws-sdk", '~> 2' # For AWS API
|
25
|
+
spec.add_dependency "highline" # For user interaction
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "minitest"
|
30
|
+
end
|
data/lib/deploy.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'highline'
|
3
|
+
require 'deploy/eb/application'
|
4
|
+
require 'deploy/eb/configuration'
|
5
|
+
require 'deploy/eb/platform'
|
6
|
+
require 'deploy/eb/state'
|
7
|
+
require 'deploy/iam/client'
|
8
|
+
require 'deploy/s3/configuration'
|
9
|
+
require 'deploy/s3/state'
|
10
|
+
require 'deploy/s3/platform'
|
11
|
+
require 'deploy/error_handler'
|
12
|
+
require 'deploy/repository'
|
13
|
+
require 'deploy/version'
|
14
|
+
|
15
|
+
module Deploy
|
16
|
+
class Runner
|
17
|
+
def initialize(tag)
|
18
|
+
@tag = tag
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
trap_int
|
23
|
+
precheck!
|
24
|
+
validate!
|
25
|
+
perform!
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def precheck!
|
31
|
+
check_for_unstaged_changes!
|
32
|
+
check_for_changelog!
|
33
|
+
check_for_aws_access!
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate!
|
37
|
+
configure!
|
38
|
+
@name = deployment_target
|
39
|
+
@platform = verify_platform
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform!
|
43
|
+
request_confirmation!
|
44
|
+
synchronize_repo!
|
45
|
+
deploy!
|
46
|
+
end
|
47
|
+
|
48
|
+
def log(msg)
|
49
|
+
# Currently no logging mechanism besides message to stdout
|
50
|
+
puts msg
|
51
|
+
end
|
52
|
+
|
53
|
+
def trap_int
|
54
|
+
Signal.trap('INT') {
|
55
|
+
abort "\nGot Ctrl-C, exiting.\n\
|
56
|
+
You will have to abort any in-progress deployments manually."
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_for_unstaged_changes!
|
61
|
+
return unless repo.index_modified?
|
62
|
+
abort "You have staged changes! Please sort your life out mate, innit?"
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_for_changelog!
|
66
|
+
changelog_updated =
|
67
|
+
cli.agree "Now hold on there for just a second, partner. "\
|
68
|
+
"Have you updated the changelog ?"
|
69
|
+
abort 'Better hop to it then ay?' unless changelog_updated
|
70
|
+
end
|
71
|
+
|
72
|
+
def check_for_aws_access!
|
73
|
+
# Verify up AWS params, i.e. that we have access key and region.
|
74
|
+
# Do so by connecting to IAM directly
|
75
|
+
# Why IAM? If your user doesn't exist, nothing else will work.
|
76
|
+
user = IAM::Client.connection
|
77
|
+
log "You are connected as #{user}."
|
78
|
+
end
|
79
|
+
|
80
|
+
def on_beanstalk?
|
81
|
+
@on_beanstalk ||= Eb::Platform.configured?
|
82
|
+
end
|
83
|
+
|
84
|
+
def repo
|
85
|
+
@repo ||= Repository.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def synchronize_repo!
|
89
|
+
log 'Preparing the tagged release version for deployment.'
|
90
|
+
repo.prepare!(@tag)
|
91
|
+
end
|
92
|
+
|
93
|
+
def cli
|
94
|
+
@cli ||= HighLine.new
|
95
|
+
end
|
96
|
+
|
97
|
+
def configuration
|
98
|
+
@configuration ||= set_configuration
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_configuration
|
102
|
+
if on_beanstalk?
|
103
|
+
Eb::Configuration.new
|
104
|
+
else
|
105
|
+
S3::Configuration.new(config_bucket_name)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def config_bucket_name
|
110
|
+
@config_bucket_name ||= set_config_bucket_name!
|
111
|
+
end
|
112
|
+
|
113
|
+
def set_config_bucket_name!
|
114
|
+
bucket_name = ENV['S3_CONFIG_BUCKET']
|
115
|
+
unless bucket_name
|
116
|
+
fail 'Please set your S3 config bucket name in '\
|
117
|
+
'ENV[\'S3_CONFIG_BUCKET\']'
|
118
|
+
end
|
119
|
+
bucket_name
|
120
|
+
end
|
121
|
+
|
122
|
+
def configure!
|
123
|
+
return if on_beanstalk?
|
124
|
+
# Pull in and verify our deployment configurations
|
125
|
+
log "Checking available configurations... Please wait..."
|
126
|
+
configuration.verify!
|
127
|
+
unless configuration.created_folders.empty?
|
128
|
+
configuration.created_folders.each do |folder|
|
129
|
+
log "\tCreated #{folder}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
log "Check done."
|
133
|
+
end
|
134
|
+
|
135
|
+
def deployment_target
|
136
|
+
app = select_from_list(apps)
|
137
|
+
return app unless on_beanstalk?
|
138
|
+
select_from_list(eb_env_list(app))
|
139
|
+
end
|
140
|
+
|
141
|
+
def eb_env_list(app)
|
142
|
+
beanstalk_application(app).environments
|
143
|
+
end
|
144
|
+
|
145
|
+
def select_from_list(list)
|
146
|
+
# Have the user decide what to deploy
|
147
|
+
log "Configured applications are:"
|
148
|
+
name = cli.choose do |menu|
|
149
|
+
menu.prompt = "Choose application to deploy, by index or name."
|
150
|
+
menu.choices *list
|
151
|
+
end
|
152
|
+
log "Selected \"#{name}\"."
|
153
|
+
name
|
154
|
+
end
|
155
|
+
|
156
|
+
def app_bucket
|
157
|
+
configuration.config_bucket_for(@name)
|
158
|
+
end
|
159
|
+
|
160
|
+
def apps
|
161
|
+
configuration.apps
|
162
|
+
end
|
163
|
+
|
164
|
+
def eb
|
165
|
+
@eb ||= Eb::State.new(@name)
|
166
|
+
end
|
167
|
+
|
168
|
+
def s3
|
169
|
+
@s3 ||= S3::State.new(@name, app_bucket)
|
170
|
+
end
|
171
|
+
|
172
|
+
def beanstalk_application(app)
|
173
|
+
@beanstalk_application ||= Eb::Application.new(app)
|
174
|
+
end
|
175
|
+
|
176
|
+
def verify_platform
|
177
|
+
if eb.exists?
|
178
|
+
fail "EB app found but you did not \'eb init\'" unless on_beanstalk?
|
179
|
+
platform = Eb::Platform.new(eb: eb, tag: @tag)
|
180
|
+
log "Environment \'#{@name}\' found on EB."
|
181
|
+
elsif s3.exists?
|
182
|
+
platform = S3::Platform.new(s3: s3, tag: @tag)
|
183
|
+
log "Website \'#{@name}\' found on S3."
|
184
|
+
log "Config bucket version \"#{s3.version}\" selected."
|
185
|
+
end
|
186
|
+
unless platform
|
187
|
+
abort "Application given as \'#{@name}\'. "\
|
188
|
+
"EB environment \'#{@name}\' was not found. "\
|
189
|
+
"S3 bucket \'#{@name}\' was not found either. "\
|
190
|
+
"Please fix this before attempting to deploy."
|
191
|
+
end
|
192
|
+
platform
|
193
|
+
end
|
194
|
+
|
195
|
+
def request_confirmation!
|
196
|
+
confirm_launch = cli.agree "Deploy release \'#{@tag}\' to \'#{@name}\' ?"
|
197
|
+
abort 'Bailing out.' unless confirm_launch
|
198
|
+
end
|
199
|
+
|
200
|
+
def deploy!
|
201
|
+
log 'Deployment commencing.'
|
202
|
+
success = @platform.deploy!
|
203
|
+
abort "Deployment Failed or timed out. See system output." unless success
|
204
|
+
log 'All done.'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Deploy
|
2
|
+
module Eb
|
3
|
+
class Application
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
end
|
7
|
+
|
8
|
+
def environments
|
9
|
+
request = {application_name: @name}
|
10
|
+
response = elasticbeanstalk.describe_environments(request)
|
11
|
+
response.environments.map(&:environment_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def elasticbeanstalk
|
17
|
+
@elasticbeanstalk ||=
|
18
|
+
ErrorHandler.with_error_handling { Aws::ElasticBeanstalk::Client.new }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Deploy
|
2
|
+
module Eb
|
3
|
+
class Configuration
|
4
|
+
def apps
|
5
|
+
@apps ||= app_names
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def client
|
11
|
+
@client ||=
|
12
|
+
ErrorHandler.with_error_handling { Aws::ElasticBeanstalk::Client.new }
|
13
|
+
end
|
14
|
+
|
15
|
+
def apps_list
|
16
|
+
ErrorHandler.with_error_handling do
|
17
|
+
client.describe_applications[0]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def app_names
|
22
|
+
apps_list.map(&:application_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Deploy
|
4
|
+
module Eb
|
5
|
+
class Platform
|
6
|
+
def self.configured?
|
7
|
+
Open3.popen3("eb list") { |_input, _output, _error, thread|
|
8
|
+
thread.value
|
9
|
+
}.exitstatus == 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(opts)
|
13
|
+
@eb = opts[:eb]
|
14
|
+
@tag = opts[:tag]
|
15
|
+
end
|
16
|
+
|
17
|
+
def deploy!
|
18
|
+
fail "Environment NOT READY!" unless @eb.ready?
|
19
|
+
fail "Environment switch failed." unless @eb.switch
|
20
|
+
eb_deploy!
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def write_redeploy_notification
|
26
|
+
puts "Elastic Beanstalk application #{@eb.application_name}"\
|
27
|
+
" already has version #{@tag}"
|
28
|
+
puts "Assuming you do mean to redeploy, perhaps to a new target."
|
29
|
+
end
|
30
|
+
|
31
|
+
def eb_deploy!
|
32
|
+
if @eb.version_exists?(@tag)
|
33
|
+
write_redeploy_notification
|
34
|
+
system("eb deploy --version=#{@tag}")
|
35
|
+
else
|
36
|
+
system("eb deploy --label=#{@tag}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Deploy
|
2
|
+
module Eb
|
3
|
+
class State
|
4
|
+
def initialize(env)
|
5
|
+
@env = env
|
6
|
+
end
|
7
|
+
|
8
|
+
def exists?
|
9
|
+
!environment_info.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def ready?
|
13
|
+
environment_info.status.eql? 'Ready'
|
14
|
+
end
|
15
|
+
|
16
|
+
def switch
|
17
|
+
system("eb use #{@env}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def environment_info
|
21
|
+
@environment_info ||= environment_description_message.environments[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def application_name
|
25
|
+
environment_info.application_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def version_exists?(version)
|
29
|
+
request = {application_name: application_name, version_labels: [version]}
|
30
|
+
response = elasticbeanstalk.describe_application_versions(request)
|
31
|
+
! response.application_versions.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def elasticbeanstalk
|
37
|
+
@elasticbeanstalk ||=
|
38
|
+
ErrorHandler.with_error_handling { Aws::ElasticBeanstalk::Client.new }
|
39
|
+
end
|
40
|
+
|
41
|
+
def environment_description_message
|
42
|
+
ErrorHandler.with_error_handling do
|
43
|
+
elasticbeanstalk.describe_environments(
|
44
|
+
environment_names: [@env]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Deploy
|
4
|
+
class ErrorHandler
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
ERROR_MESSAGES = {
|
8
|
+
'Aws::Errors::MissingCredentialsError' => 'Missing AWS credentials. Error thrown by AWS',
|
9
|
+
'Aws::ElasticBeanstalk::Errors::ServiceError' => 'Error thrown by AWS EB',
|
10
|
+
'Aws::S3::Errors::ServiceError' => 'Error thrown by AWS S3'
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.with_error_handling(&block)
|
14
|
+
instance.with_error_handling(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_error_handling
|
18
|
+
yield
|
19
|
+
rescue RuntimeError => error
|
20
|
+
fail message_for(error)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def message_for(error)
|
26
|
+
message = ERROR_MESSAGES[error.class.name] || 'Unknown error'
|
27
|
+
"#{message}: #{error}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Deploy
|
2
|
+
module IAM
|
3
|
+
class Client
|
4
|
+
|
5
|
+
REGION_ERROR='Please configure your AWS region in ENV[\'AWS_REGION\'].'
|
6
|
+
CREDENTIALS_ERROR='Please set your AWS credentials.
|
7
|
+
Credentials are read from ~/.aws/credentials, '\
|
8
|
+
'if possible.
|
9
|
+
Otherwise, credentials are loaded from '\
|
10
|
+
'ENV[\'AWS_ACCESS_KEY_ID\'] and '\
|
11
|
+
'ENV[\'AWS_SECRET_ACCESS_KEY\'].'
|
12
|
+
|
13
|
+
def self.connection
|
14
|
+
Aws::IAM::CurrentUser.new.arn
|
15
|
+
rescue Aws::Errors::MissingRegionError
|
16
|
+
fail ArgumentError, REGION_ERROR
|
17
|
+
rescue Aws::Errors::MissingCredentialsError
|
18
|
+
fail ArgumentError, CREDENTIALS_ERROR
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Deploy
|
5
|
+
class Repository
|
6
|
+
def index_modified?
|
7
|
+
! system('git diff-index --cached --quiet HEAD --ignore-submodules --')
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare!(tag)
|
11
|
+
fail "No tag given" unless tag
|
12
|
+
@tag = tag
|
13
|
+
sync! unless tag_exists?
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def tag_exists?
|
19
|
+
Open3.popen3("git rev-parse #{@tag}") { |_input, _output, error, _thread|
|
20
|
+
error.read.chomp
|
21
|
+
}.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def sync!
|
25
|
+
version!
|
26
|
+
commit!
|
27
|
+
tag!
|
28
|
+
push!
|
29
|
+
end
|
30
|
+
|
31
|
+
def version!
|
32
|
+
FileUtils.mkdir_p 'public'
|
33
|
+
File.open('public/version.txt', 'w') { |file| file.puts(@tag) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def commit!
|
37
|
+
puts "Committing version.txt..."
|
38
|
+
unless system('git add public/version.txt') && system("git commit -m \"#{commit_message}\" ")
|
39
|
+
fail "Failed to commit."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def tag!
|
44
|
+
puts "Tagging #{@tag} as new version..."
|
45
|
+
unless system("git tag -a #{@tag} -m \"#{tag_message}\" ")
|
46
|
+
fail "Failed to tag"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def push!
|
51
|
+
puts "Pushing changes to origin..."
|
52
|
+
unless system('git push origin HEAD') && system('git push origin --tags')
|
53
|
+
fail "Failed to git push."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def tag_message
|
58
|
+
"Deployed #{@tag}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def commit_message
|
62
|
+
@commit_message ||= "#{last_commit_message} - deploy"
|
63
|
+
end
|
64
|
+
|
65
|
+
def last_commit_message
|
66
|
+
@last_commit_message ||= `git log --pretty=%B -1`.chomp('')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Deploy
|
2
|
+
module S3
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
def initialize(config_bucket_name)
|
6
|
+
@config_bucket_name = config_bucket_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def verify!
|
10
|
+
unless config_bucket.exists? && objects.count > 0
|
11
|
+
fail "Configuration bucket #{@config_bucket_name} not found or empty."
|
12
|
+
end
|
13
|
+
enforce_valid_app_paths!
|
14
|
+
@verified = true
|
15
|
+
end
|
16
|
+
|
17
|
+
def apps
|
18
|
+
fail "Asked for app list without verifying. It will be wrong." if !@verified
|
19
|
+
@apps ||= app_names
|
20
|
+
end
|
21
|
+
|
22
|
+
def created_folders
|
23
|
+
@created_folders ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
def config_bucket_for(name)
|
27
|
+
app_buckets.detect { |app| app.key == name + '/' }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def config_bucket
|
33
|
+
@config_bucket ||=
|
34
|
+
ErrorHandler.with_error_handling { Aws::S3::Bucket.new(@config_bucket_name) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def objects
|
38
|
+
@objects ||= ErrorHandler.with_error_handling { config_bucket.objects }
|
39
|
+
end
|
40
|
+
|
41
|
+
def client
|
42
|
+
@client ||= ErrorHandler.with_error_handling { Aws::S3::Client.new }
|
43
|
+
end
|
44
|
+
|
45
|
+
def enforce_valid_app_paths!
|
46
|
+
# check folders in our config bucket, recreate any missing folders
|
47
|
+
object_names = objects.map(&:key)
|
48
|
+
file_names = object_names.select { |name| !name.end_with?('/') }
|
49
|
+
max_depth = object_names.map { |name| name.count('/') }.max
|
50
|
+
possible_object_names = (1..max_depth).reduce([]) { |list, depth|
|
51
|
+
list += object_names.map { |name| name.split('/').first(depth).join('/') }
|
52
|
+
}
|
53
|
+
folder_names = possible_object_names.uniq - file_names
|
54
|
+
path_names = folder_names.map { |folder| folder + '/' }
|
55
|
+
|
56
|
+
# Make the folder if needed
|
57
|
+
folders_to_create = path_names.
|
58
|
+
sort_by { |name| name.count('/') }.
|
59
|
+
select{|folder| ! folder_exists?(folder) }
|
60
|
+
folders_to_create.each do |folder|
|
61
|
+
create_folder!(folder)
|
62
|
+
end
|
63
|
+
|
64
|
+
@created_folders = folders_to_create
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_folder!(folder)
|
68
|
+
ErrorHandler.with_error_handling do
|
69
|
+
client.put_object(
|
70
|
+
acl: 'private',
|
71
|
+
body: nil,
|
72
|
+
bucket: config_bucket.name,
|
73
|
+
key: folder
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def folder_exists?(folder)
|
79
|
+
ErrorHandler.with_error_handling do
|
80
|
+
config_bucket.object(folder).exists?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def app_buckets
|
85
|
+
@app_buckets ||= app_buckets_list
|
86
|
+
end
|
87
|
+
|
88
|
+
def app_buckets_list
|
89
|
+
ErrorHandler.with_error_handling do
|
90
|
+
objects.select do |o|
|
91
|
+
!o.key.empty? &&
|
92
|
+
o.key.end_with?('/') &&
|
93
|
+
o.key.count('/') == 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def app_names
|
99
|
+
app_buckets.map{|o| o.key.sub('/', '') }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Deploy
|
2
|
+
module S3
|
3
|
+
class Platform
|
4
|
+
def initialize(opts)
|
5
|
+
@s3 = opts[:s3]
|
6
|
+
@tag = opts[:tag]
|
7
|
+
end
|
8
|
+
|
9
|
+
def deploy!
|
10
|
+
# TODO: Add option to re-use existing deployment if we can
|
11
|
+
s3_deploy!
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def s3_deploy!
|
17
|
+
system(
|
18
|
+
"bucket=#{@s3.target}"\
|
19
|
+
" s3_config_version=#{@s3.version}"\
|
20
|
+
" npm run publish"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Deploy
|
2
|
+
module S3
|
3
|
+
class State
|
4
|
+
def initialize(name, app_configs)
|
5
|
+
@name = name
|
6
|
+
@app_configs = app_configs
|
7
|
+
end
|
8
|
+
|
9
|
+
def exists?
|
10
|
+
target_bucket.exists?
|
11
|
+
end
|
12
|
+
|
13
|
+
def version
|
14
|
+
@version ||= version_select
|
15
|
+
end
|
16
|
+
|
17
|
+
def target
|
18
|
+
target_bucket.name
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def target_bucket
|
24
|
+
ErrorHandler.with_error_handling do
|
25
|
+
Aws::S3::Bucket.new(name: @name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def version_select
|
30
|
+
# Provide the configuration versions and let user choose
|
31
|
+
versions = version_folders.map{|obj| obj.key.split('/').last }
|
32
|
+
|
33
|
+
puts "Found configuration versions:"
|
34
|
+
cli = HighLine.new
|
35
|
+
cli.choose do |menu|
|
36
|
+
menu.prompt = "Select index of the configuration to use:"
|
37
|
+
menu.choices *versions
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def version_folders
|
42
|
+
@version_folders ||= read_version_folders
|
43
|
+
end
|
44
|
+
|
45
|
+
# The assumed structure of a config folder in S3 is:
|
46
|
+
# sealink-config/<app>/config/<version>
|
47
|
+
def read_version_folders
|
48
|
+
ErrorHandler.with_error_handling do
|
49
|
+
@app_configs.bucket.objects.select do |o|
|
50
|
+
!o.key.empty? &&
|
51
|
+
o.key.start_with?("#{@name}/config/") &&
|
52
|
+
o.key.end_with?('/') &&
|
53
|
+
o.key.count('/') == 3
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deploy_aws
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Smirnoff
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: highline
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Deploys from current Git repo to AWS EB or S3 (via node)
|
84
|
+
email:
|
85
|
+
- michael.smirnoff@sealink.com.au
|
86
|
+
executables:
|
87
|
+
- console
|
88
|
+
- deploy
|
89
|
+
- setup
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files: []
|
92
|
+
files:
|
93
|
+
- ".gitignore"
|
94
|
+
- ".ruby-version"
|
95
|
+
- ".travis.yml"
|
96
|
+
- CODE_OF_CONDUCT.md
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- bin/console
|
102
|
+
- bin/deploy
|
103
|
+
- bin/setup
|
104
|
+
- deploy_aws.gemspec
|
105
|
+
- lib/deploy.rb
|
106
|
+
- lib/deploy/eb/application.rb
|
107
|
+
- lib/deploy/eb/configuration.rb
|
108
|
+
- lib/deploy/eb/platform.rb
|
109
|
+
- lib/deploy/eb/state.rb
|
110
|
+
- lib/deploy/error_handler.rb
|
111
|
+
- lib/deploy/iam/client.rb
|
112
|
+
- lib/deploy/repository.rb
|
113
|
+
- lib/deploy/s3/configuration.rb
|
114
|
+
- lib/deploy/s3/platform.rb
|
115
|
+
- lib/deploy/s3/state.rb
|
116
|
+
- lib/deploy/version.rb
|
117
|
+
homepage: https://github.com/sealink/deploy_aws
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.4.5.1
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Deploys from current Git repo to AWS
|
141
|
+
test_files: []
|