forger 1.5.0
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 +7 -0
- data/.gitignore +16 -0
- data/.gitmodules +0 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +147 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +136 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +249 -0
- data/Rakefile +6 -0
- data/docs/example/.env +2 -0
- data/docs/example/.env.development +2 -0
- data/docs/example/.env.production +3 -0
- data/docs/example/app/scripts/hello.sh +3 -0
- data/docs/example/app/user-data/bootstrap.sh +35 -0
- data/docs/example/config/development.yml +8 -0
- data/docs/example/profiles/default.yml +11 -0
- data/docs/example/profiles/spot.yml +20 -0
- data/exe/forger +14 -0
- data/forger.gemspec +38 -0
- data/lib/forger.rb +29 -0
- data/lib/forger/ami.rb +10 -0
- data/lib/forger/aws_service.rb +7 -0
- data/lib/forger/base.rb +42 -0
- data/lib/forger/clean.rb +13 -0
- data/lib/forger/cleaner.rb +5 -0
- data/lib/forger/cleaner/ami.rb +45 -0
- data/lib/forger/cli.rb +67 -0
- data/lib/forger/command.rb +67 -0
- data/lib/forger/completer.rb +161 -0
- data/lib/forger/completer/script.rb +6 -0
- data/lib/forger/completer/script.sh +10 -0
- data/lib/forger/config.rb +20 -0
- data/lib/forger/core.rb +51 -0
- data/lib/forger/create.rb +155 -0
- data/lib/forger/create/error_messages.rb +58 -0
- data/lib/forger/create/params.rb +106 -0
- data/lib/forger/dotenv.rb +30 -0
- data/lib/forger/help.rb +9 -0
- data/lib/forger/help/ami.md +13 -0
- data/lib/forger/help/clean/ami.md +22 -0
- data/lib/forger/help/compile.md +5 -0
- data/lib/forger/help/completion.md +22 -0
- data/lib/forger/help/completion_script.md +3 -0
- data/lib/forger/help/create.md +7 -0
- data/lib/forger/help/upload.md +10 -0
- data/lib/forger/help/wait/ami.md +12 -0
- data/lib/forger/hook.rb +33 -0
- data/lib/forger/profile.rb +64 -0
- data/lib/forger/script.rb +46 -0
- data/lib/forger/script/compile.rb +40 -0
- data/lib/forger/script/compress.rb +62 -0
- data/lib/forger/script/templates/ami_creation.sh +12 -0
- data/lib/forger/script/templates/auto_terminate.sh +11 -0
- data/lib/forger/script/templates/auto_terminate_after_timeout.sh +5 -0
- data/lib/forger/script/templates/cloudwatch.sh +3 -0
- data/lib/forger/script/templates/extract_aws_ec2_scripts.sh +48 -0
- data/lib/forger/script/upload.rb +99 -0
- data/lib/forger/scripts/auto_terminate.sh +14 -0
- data/lib/forger/scripts/auto_terminate/after_timeout.sh +18 -0
- data/lib/forger/scripts/auto_terminate/functions.sh +130 -0
- data/lib/forger/scripts/auto_terminate/functions/amazonlinux2.sh +10 -0
- data/lib/forger/scripts/auto_terminate/functions/ubuntu.sh +11 -0
- data/lib/forger/scripts/auto_terminate/setup.sh +31 -0
- data/lib/forger/scripts/cloudwatch.sh +24 -0
- data/lib/forger/scripts/cloudwatch/configure.sh +84 -0
- data/lib/forger/scripts/cloudwatch/install.sh +3 -0
- data/lib/forger/scripts/cloudwatch/install/amazonlinux2.sh +4 -0
- data/lib/forger/scripts/cloudwatch/install/ubuntu.sh +23 -0
- data/lib/forger/scripts/cloudwatch/service.sh +3 -0
- data/lib/forger/scripts/cloudwatch/service/amazonlinux2.sh +11 -0
- data/lib/forger/scripts/cloudwatch/service/ubuntu.sh +8 -0
- data/lib/forger/scripts/shared/functions.sh +78 -0
- data/lib/forger/setting.rb +52 -0
- data/lib/forger/template.rb +13 -0
- data/lib/forger/template/context.rb +32 -0
- data/lib/forger/template/helper.rb +17 -0
- data/lib/forger/template/helper/ami_helper.rb +33 -0
- data/lib/forger/template/helper/core_helper.rb +127 -0
- data/lib/forger/template/helper/partial_helper.rb +71 -0
- data/lib/forger/template/helper/script_helper.rb +53 -0
- data/lib/forger/template/helper/ssh_key_helper.rb +21 -0
- data/lib/forger/version.rb +3 -0
- data/lib/forger/wait.rb +12 -0
- data/lib/forger/waiter.rb +5 -0
- data/lib/forger/waiter/ami.rb +61 -0
- data/spec/fixtures/demo_project/config/settings.yml +22 -0
- data/spec/fixtures/demo_project/config/test.yml +9 -0
- data/spec/fixtures/demo_project/profiles/default.yml +33 -0
- data/spec/lib/cli_spec.rb +41 -0
- data/spec/lib/params_spec.rb +71 -0
- data/spec/spec_helper.rb +33 -0
- metadata +354 -0
data/Rakefile
ADDED
data/docs/example/.env
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/bash -exu
|
2
|
+
|
3
|
+
export HOME=/root # user-data env runs in weird shell where user is root but HOME is not set
|
4
|
+
|
5
|
+
sudo yum install -y postgresql
|
6
|
+
|
7
|
+
# https://gist.github.com/juno/1330165
|
8
|
+
# Install developer tools
|
9
|
+
yum install -y git gcc make readline-devel openssl-devel
|
10
|
+
|
11
|
+
# Install ruby-build system-widely
|
12
|
+
git clone git://github.com/sstephenson/ruby-build.git /tmp/ruby-build
|
13
|
+
cd /tmp/ruby-build
|
14
|
+
./install.sh
|
15
|
+
echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.bashrc
|
16
|
+
|
17
|
+
# Install rbenv for root
|
18
|
+
git clone git://github.com/sstephenson/rbenv.git ~/.rbenv
|
19
|
+
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
|
20
|
+
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
21
|
+
set +u
|
22
|
+
source ~/.bashrc
|
23
|
+
set -u
|
24
|
+
|
25
|
+
# Install and enable ruby
|
26
|
+
rbenv install 2.5.0
|
27
|
+
|
28
|
+
# Install ruby for ec2-user also
|
29
|
+
cp -R ~/.rbenv /home/ec2-user/
|
30
|
+
chown -R ec2-user:ec2-user /home/ec2-user/.rbenv
|
31
|
+
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> /home/ec2-user/.bashrc
|
32
|
+
echo 'eval "$(rbenv init -)"' >> /home/ec2-user/.bashrc
|
33
|
+
echo '2.5.0' > /home/ec2-user/.ruby-version
|
34
|
+
|
35
|
+
uptime | tee /var/log/boot-time.log
|
@@ -0,0 +1,11 @@
|
|
1
|
+
---
|
2
|
+
image_id: ami-97785bed
|
3
|
+
instance_type: t2.medium
|
4
|
+
key_name: default
|
5
|
+
max_count: 1
|
6
|
+
min_count: 1
|
7
|
+
security_group_ids: <%= config["security_group_ids"] %>
|
8
|
+
subnet_id: <%= config["subnets"].shuffle.first %>
|
9
|
+
user_data: "<%= user_data("bootstrap") %>"
|
10
|
+
iam_instance_profile:
|
11
|
+
name: IAMProfileName
|
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
image_id: ami-97785bed
|
3
|
+
instance_type: t2.medium
|
4
|
+
key_name: default
|
5
|
+
max_count: 1
|
6
|
+
min_count: 1
|
7
|
+
security_group_ids:
|
8
|
+
- sg-111
|
9
|
+
subnet_id: <%= %w[subnet-111 subnet-222].shuffle.first %>
|
10
|
+
user_data: "<%= user_data("dev") %>"
|
11
|
+
iam_instance_profile:
|
12
|
+
name: IAMProfileName
|
13
|
+
instance_market_options:
|
14
|
+
market_type: spot
|
15
|
+
# https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_LaunchTemplateSpotMarketOptionsRequest.html
|
16
|
+
# https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/EC2/Types/SpotMarketOptions.html
|
17
|
+
spot_options:
|
18
|
+
max_price: "0.02"
|
19
|
+
spot_instance_type: one-time
|
20
|
+
# instance_interruption_behavior: hibernate
|
data/exe/forger
ADDED
data/forger.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "forger/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "forger"
|
8
|
+
spec.version = Forger::VERSION
|
9
|
+
spec.authors = ["Tung Nguyen"]
|
10
|
+
spec.email = ["tongueroo@gmail.com"]
|
11
|
+
spec.description = %q{Simple tool to create AWS ec2 instances consistently with pre-configured settings}
|
12
|
+
spec.summary = %q{Simple tool to create AWS ec2 instances}
|
13
|
+
spec.homepage = "https://github.com/tongueroo/forger"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
spec.add_dependency "aws-sdk-ec2"
|
24
|
+
spec.add_dependency "aws-sdk-s3"
|
25
|
+
spec.add_dependency "colorize"
|
26
|
+
spec.add_dependency "dotenv"
|
27
|
+
spec.add_dependency "filesize"
|
28
|
+
spec.add_dependency "hashie"
|
29
|
+
spec.add_dependency "render_me_pretty"
|
30
|
+
spec.add_dependency "thor"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler"
|
33
|
+
spec.add_development_dependency "byebug"
|
34
|
+
spec.add_development_dependency "guard"
|
35
|
+
spec.add_development_dependency "guard-bundler"
|
36
|
+
spec.add_development_dependency "guard-rspec"
|
37
|
+
spec.add_development_dependency "rake"
|
38
|
+
end
|
data/lib/forger.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$:.unshift(File.expand_path("../", __FILE__))
|
2
|
+
require "forger/version"
|
3
|
+
require "colorize"
|
4
|
+
require "render_me_pretty"
|
5
|
+
|
6
|
+
module Forger
|
7
|
+
autoload :Help, "forger/help"
|
8
|
+
autoload :Command, "forger/command"
|
9
|
+
autoload :CLI, "forger/cli"
|
10
|
+
autoload :AwsService, "forger/aws_service"
|
11
|
+
autoload :Profile, "forger/profile"
|
12
|
+
autoload :Base, "forger/base"
|
13
|
+
autoload :Create, "forger/create"
|
14
|
+
autoload :Ami, "forger/ami"
|
15
|
+
autoload :Wait, "forger/wait"
|
16
|
+
autoload :Clean, "forger/clean"
|
17
|
+
autoload :Template, "forger/template"
|
18
|
+
autoload :Script, "forger/script"
|
19
|
+
autoload :Config, "forger/config"
|
20
|
+
autoload :Core, "forger/core"
|
21
|
+
autoload :Dotenv, "forger/dotenv"
|
22
|
+
autoload :Hook, "forger/hook"
|
23
|
+
autoload :Completion, "forger/completion"
|
24
|
+
autoload :Completer, "forger/completer"
|
25
|
+
autoload :Setting, "forger/setting"
|
26
|
+
extend Core
|
27
|
+
end
|
28
|
+
|
29
|
+
Forger::Dotenv.load!
|
data/lib/forger/ami.rb
ADDED
data/lib/forger/base.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Forger
|
2
|
+
class Base
|
3
|
+
# constants really only used by script classes
|
4
|
+
SCRIPTS_INFO_PATH = "tmp/data/scripts_info.txt"
|
5
|
+
BUILD_ROOT = "tmp"
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
@options = options.clone
|
9
|
+
@name = randomize(@options[:name])
|
10
|
+
Forger.validate_in_project!
|
11
|
+
end
|
12
|
+
|
13
|
+
# Appends a short random string at the end of the ec2 instance name.
|
14
|
+
# Later we will strip this same random string from the name.
|
15
|
+
# Very makes it convenient. We can just type:
|
16
|
+
#
|
17
|
+
# forger create server --randomize
|
18
|
+
#
|
19
|
+
# instead of:
|
20
|
+
#
|
21
|
+
# forger create server-123 --profile server
|
22
|
+
#
|
23
|
+
def randomize(name)
|
24
|
+
if @options[:randomize]
|
25
|
+
random = (0...3).map { (65 + rand(26)).chr }.join.downcase # Ex: jhx
|
26
|
+
[name, random].join('-')
|
27
|
+
else
|
28
|
+
name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Strip the random string at end of the ec2 instance name
|
33
|
+
def derandomize(name)
|
34
|
+
if @options[:randomize]
|
35
|
+
name.sub(/-(\w{3})$/,'') # strip the random part at the end
|
36
|
+
else
|
37
|
+
name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
data/lib/forger/clean.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Forger
|
2
|
+
autoload :Cleaner, 'forger/cleaner'
|
3
|
+
|
4
|
+
class Clean < Command
|
5
|
+
desc "ami", "Clean until AMI available."
|
6
|
+
long_desc Help.text("clean:ami")
|
7
|
+
option :keep, type: :numeric, default: 2, desc: "Number of images to keep"
|
8
|
+
option :noop, type: :boolean, desc: "Noop or dry-run mode"
|
9
|
+
def ami(query)
|
10
|
+
Cleaner::Ami.new(options.merge(query: query)).clean
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Forger::Cleaner
|
2
|
+
class Ami < Forger::Base
|
3
|
+
include Forger::AwsService
|
4
|
+
|
5
|
+
def clean
|
6
|
+
query = @options[:query]
|
7
|
+
keep = @options[:keep] || 2
|
8
|
+
puts "Cleaning out old AMIs with base name: #{@options[:query]}"
|
9
|
+
return if ENV['TEST']
|
10
|
+
|
11
|
+
images = search_ami(query)
|
12
|
+
images = images.sort_by { |i| i.name }.reverse
|
13
|
+
delete_list = images[keep..-1] || []
|
14
|
+
puts "Deleting #{delete_list.size} images."
|
15
|
+
delete_list.each do |i|
|
16
|
+
delete(i)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def delete(image)
|
22
|
+
message = "Deleting image: #{image.image_id} #{image.name}"
|
23
|
+
if @options[:noop]
|
24
|
+
puts "NOOP: #{message}"
|
25
|
+
else
|
26
|
+
puts message
|
27
|
+
ec2.deregister_image(image_id: image.image_id)
|
28
|
+
end
|
29
|
+
rescue Aws::EC2::Errors::InvalidAMIIDUnavailable
|
30
|
+
# happens when image was just deleted but its still
|
31
|
+
# showing up as available when calling describe_images
|
32
|
+
puts "WARN: #{e.message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def search_ami(query, owners=["self"])
|
36
|
+
ec2.describe_images(
|
37
|
+
owners: owners,
|
38
|
+
filters: [
|
39
|
+
{name: "name", values: [query]},
|
40
|
+
{name: "state", values: ["available"]}
|
41
|
+
]
|
42
|
+
).images
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/forger/cli.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Forger
|
2
|
+
class CLI < Command
|
3
|
+
class_option :noop, type: :boolean
|
4
|
+
class_option :profile, desc: "profile name to use"
|
5
|
+
|
6
|
+
desc "clean SUBCOMMAND", "clean subcommands"
|
7
|
+
long_desc Help.text(:clean)
|
8
|
+
subcommand "clean", Clean
|
9
|
+
|
10
|
+
desc "wait SUBCOMMAND", "wait subcommands"
|
11
|
+
long_desc Help.text(:wait)
|
12
|
+
subcommand "wait", Wait
|
13
|
+
|
14
|
+
common_options = Proc.new do
|
15
|
+
option :auto_terminate, type: :boolean, default: false, desc: "automatically terminate the instance at the end of user-data"
|
16
|
+
option :cloudwatch, type: :boolean, default: false, desc: "enable cloudwatch logging, supported for amazonlinux2 and ubuntu"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "create NAME", "create ec2 instance"
|
20
|
+
long_desc Help.text(:create)
|
21
|
+
option :ami_name, desc: "when specified, an ami creation script is appended to the user-data script"
|
22
|
+
option :randomize, type: :boolean, desc: "append random characters to end of name"
|
23
|
+
option :source_ami, desc: "override the source image_id in profile"
|
24
|
+
common_options.call
|
25
|
+
def create(name)
|
26
|
+
Create.new(options.merge(name: name)).run
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "ami NAME", "launches instance and uses it create AMI"
|
30
|
+
long_desc Help.text(:ami)
|
31
|
+
common_options.call
|
32
|
+
def ami(name)
|
33
|
+
Ami.new(options.merge(name: name)).run
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "compile", "compiles app/scripts and app/user-data to tmp folder"
|
37
|
+
long_desc Help.text(:compile)
|
38
|
+
option :layout, default: "default", desc: "layout for user_data helper"
|
39
|
+
def compile
|
40
|
+
Script::Compile.new(options).compile_all
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "upload", "compiles and uploads scripts to s3"
|
44
|
+
long_desc Help.text(:upload)
|
45
|
+
option :compile, type: :boolean, default: true, desc: "compile scripts before uploading"
|
46
|
+
def upload
|
47
|
+
Script::Upload.new(options).upload
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "completion *PARAMS", "Prints words for auto-completion."
|
51
|
+
long_desc Help.text("completion")
|
52
|
+
def completion(*params)
|
53
|
+
Completer.new(CLI, *params).run
|
54
|
+
end
|
55
|
+
|
56
|
+
desc "completion_script", "Generates a script that can be eval to setup auto-completion."
|
57
|
+
long_desc Help.text("completion_script")
|
58
|
+
def completion_script
|
59
|
+
Completer::Script.generate
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "version", "prints version"
|
63
|
+
def version
|
64
|
+
puts VERSION
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
# Override thor's long_desc identation behavior
|
4
|
+
# https://github.com/erikhuda/thor/issues/398
|
5
|
+
class Thor
|
6
|
+
module Shell
|
7
|
+
class Basic
|
8
|
+
def print_wrapped(message, options = {})
|
9
|
+
message = "\n#{message}" unless message[0] == "\n"
|
10
|
+
stdout.puts message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Forger
|
17
|
+
class Command < Thor
|
18
|
+
class << self
|
19
|
+
def dispatch(m, args, options, config)
|
20
|
+
# Allow calling for help via:
|
21
|
+
# forger command help
|
22
|
+
# forger command -h
|
23
|
+
# forger command --help
|
24
|
+
# forger command -D
|
25
|
+
#
|
26
|
+
# as well thor's normal way:
|
27
|
+
#
|
28
|
+
# forger help command
|
29
|
+
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
30
|
+
if args.length > 1 && !(args & help_flags).empty?
|
31
|
+
args -= help_flags
|
32
|
+
args.insert(-2, "help")
|
33
|
+
end
|
34
|
+
|
35
|
+
# forger version
|
36
|
+
# forger --version
|
37
|
+
# forger -v
|
38
|
+
version_flags = ["--version", "-v"]
|
39
|
+
if args.length == 1 && !(args & version_flags).empty?
|
40
|
+
args = ["version"]
|
41
|
+
end
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override command_help to include the description at the top of the
|
47
|
+
# long_description.
|
48
|
+
def command_help(shell, command_name)
|
49
|
+
meth = normalize_command_name(command_name)
|
50
|
+
command = all_commands[meth]
|
51
|
+
alter_command_description(command)
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def alter_command_description(command)
|
56
|
+
return unless command
|
57
|
+
long_desc = if command.long_description
|
58
|
+
"#{command.description}\n\n#{command.long_description}"
|
59
|
+
else
|
60
|
+
command.description
|
61
|
+
end
|
62
|
+
command.long_description = long_desc
|
63
|
+
end
|
64
|
+
private :alter_command_description
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|