forger 2.0.5 → 3.0.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 +4 -4
- data/.circleci/config.yml +1 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +40 -29
- data/README.md +6 -37
- data/docs/example/config/variables/development.rb +2 -0
- data/docs/example/profiles/default.yml +2 -2
- data/docs/extract-scripts.md +40 -0
- data/docs/layouts.md +35 -0
- data/docs/profiles.md +79 -0
- data/docs/variables.md +53 -0
- data/forger.gemspec +6 -3
- data/lib/forger.rb +4 -23
- data/lib/forger/autoloader.rb +21 -0
- data/lib/forger/aws_services.rb +22 -0
- data/lib/forger/clean.rb +0 -2
- data/lib/forger/cleaner.rb +0 -1
- data/lib/forger/cleaner/ami.rb +1 -1
- data/lib/forger/cli.rb +10 -6
- data/lib/forger/completer.rb +0 -2
- data/lib/forger/core.rb +28 -12
- data/lib/forger/create.rb +2 -23
- data/lib/forger/create/info.rb +10 -4
- data/lib/forger/create/waiter.rb +1 -1
- data/lib/forger/destroy.rb +1 -1
- data/lib/forger/help/upload.md +1 -13
- data/lib/forger/network.rb +2 -2
- data/lib/forger/new.rb +5 -6
- data/lib/forger/profile.rb +15 -3
- data/lib/forger/s3.rb +23 -0
- data/lib/forger/s3/bucket.rb +131 -0
- data/lib/forger/script.rb +0 -4
- data/lib/forger/script/upload.rb +12 -42
- data/lib/forger/scripts/cloudwatch.sh +2 -2
- data/lib/forger/scripts/shared/functions.sh +1 -1
- data/lib/forger/setting.rb +0 -32
- data/lib/forger/template.rb +0 -3
- data/lib/forger/template/context.rb +16 -1
- data/lib/forger/template/helper.rb +9 -9
- data/lib/forger/template/helper/ami_helper.rb +1 -1
- data/lib/forger/template/helper/core_helper.rb +7 -6
- data/lib/forger/template/helper/script_helper.rb +3 -9
- data/lib/forger/version.rb +1 -1
- data/lib/forger/wait.rb +0 -2
- data/lib/forger/waiter.rb +0 -1
- data/lib/forger/waiter/ami.rb +1 -1
- data/lib/templates/default/app/user_data/bootstrap.sh.tt +6 -9
- data/lib/templates/default/app/user_data/layouts/default.sh.tt +1 -5
- data/lib/templates/default/config/settings.yml.tt +1 -11
- data/lib/templates/default/config/{development.yml.tt → variables/development.yml.tt} +0 -0
- data/spec/fixtures/demo_project/app/user_data/bootstrap.sh +2 -2
- data/spec/fixtures/demo_project/config/settings.yml +2 -4
- data/spec/fixtures/demo_project/config/variables/test.rb +4 -0
- data/spec/fixtures/demo_project/profiles/default.yml +2 -2
- metadata +59 -13
- data/docs/example/config/development.yml +0 -7
- data/lib/forger/aws_service.rb +0 -7
- data/lib/forger/config.rb +0 -25
- data/spec/fixtures/demo_project/config/test.yml +0 -8
data/forger.gemspec
CHANGED
@@ -6,9 +6,9 @@ require "forger/version"
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "forger"
|
8
8
|
spec.version = Forger::VERSION
|
9
|
-
spec.
|
10
|
-
spec.email =
|
11
|
-
spec.summary = "
|
9
|
+
spec.author = "Tung Nguyen"
|
10
|
+
spec.email = "tongueroo@gmail.com"
|
11
|
+
spec.summary = "Create EC2 Instances with preconfigured settings"
|
12
12
|
spec.homepage = "https://github.com/tongueroo/forger"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
@@ -19,8 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_dependency "activesupport"
|
22
|
+
spec.add_dependency "aws-sdk-cloudformation"
|
22
23
|
spec.add_dependency "aws-sdk-ec2"
|
23
24
|
spec.add_dependency "aws-sdk-s3"
|
25
|
+
spec.add_dependency "cfn-status"
|
24
26
|
spec.add_dependency "dotenv"
|
25
27
|
spec.add_dependency "filesize"
|
26
28
|
spec.add_dependency "hashie"
|
@@ -28,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
28
30
|
spec.add_dependency "rainbow"
|
29
31
|
spec.add_dependency "render_me_pretty"
|
30
32
|
spec.add_dependency "thor"
|
33
|
+
spec.add_dependency "zeitwerk"
|
31
34
|
|
32
35
|
spec.add_development_dependency "bundler"
|
33
36
|
spec.add_development_dependency "byebug"
|
data/lib/forger.rb
CHANGED
@@ -4,31 +4,12 @@ require "rainbow/ext/string"
|
|
4
4
|
require "render_me_pretty"
|
5
5
|
require "memoist"
|
6
6
|
|
7
|
+
require "forger/autoloader"
|
8
|
+
Forger::Autoloader.setup
|
9
|
+
|
7
10
|
module Forger
|
8
|
-
autoload :Ami, "forger/ami"
|
9
|
-
autoload :AwsService, "forger/aws_service"
|
10
|
-
autoload :Base, "forger/base"
|
11
|
-
autoload :Clean, "forger/clean"
|
12
|
-
autoload :CLI, "forger/cli"
|
13
|
-
autoload :Command, "forger/command"
|
14
|
-
autoload :Completer, "forger/completer"
|
15
|
-
autoload :Completion, "forger/completion"
|
16
|
-
autoload :Config, "forger/config"
|
17
|
-
autoload :Core, "forger/core"
|
18
|
-
autoload :Create, "forger/create"
|
19
|
-
autoload :Destroy, "forger/destroy"
|
20
|
-
autoload :Dotenv, "forger/dotenv"
|
21
|
-
autoload :Help, "forger/help"
|
22
|
-
autoload :Hook, "forger/hook"
|
23
|
-
autoload :Network, "forger/network"
|
24
|
-
autoload :New, "forger/new"
|
25
|
-
autoload :Profile, "forger/profile"
|
26
|
-
autoload :Script, "forger/script"
|
27
|
-
autoload :Sequence, "forger/sequence"
|
28
|
-
autoload :Setting, "forger/setting"
|
29
|
-
autoload :Template, "forger/template"
|
30
|
-
autoload :Wait, "forger/wait"
|
31
11
|
extend Core
|
32
12
|
end
|
33
13
|
|
34
14
|
Forger::Dotenv.load!
|
15
|
+
Forger.set_aws_profile!
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
module Forger
|
4
|
+
class Autoloader
|
5
|
+
class Inflector < Zeitwerk::Inflector
|
6
|
+
def camelize(basename, _abspath)
|
7
|
+
map = { cli: "CLI", version: "VERSION" }
|
8
|
+
map[basename.to_sym] || super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def setup
|
14
|
+
loader = Zeitwerk::Loader.new
|
15
|
+
loader.inflector = Inflector.new
|
16
|
+
loader.push_dir(File.dirname(__dir__)) # lib
|
17
|
+
loader.setup
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'aws-sdk-cloudformation'
|
2
|
+
require 'aws-sdk-ec2'
|
3
|
+
require 'aws-sdk-s3'
|
4
|
+
|
5
|
+
module Forger::AwsServices
|
6
|
+
extend Memoist
|
7
|
+
|
8
|
+
def cfn
|
9
|
+
Aws::CloudFormation::Client.new
|
10
|
+
end
|
11
|
+
memoize :cfn
|
12
|
+
|
13
|
+
def ec2
|
14
|
+
Aws::EC2::Client.new
|
15
|
+
end
|
16
|
+
memoize :ec2
|
17
|
+
|
18
|
+
def s3
|
19
|
+
Aws::S3::Client.new
|
20
|
+
end
|
21
|
+
memoize :s3
|
22
|
+
end
|
data/lib/forger/clean.rb
CHANGED
data/lib/forger/cleaner.rb
CHANGED
data/lib/forger/cleaner/ami.rb
CHANGED
data/lib/forger/cli.rb
CHANGED
@@ -14,6 +14,12 @@ module Forger
|
|
14
14
|
common_options = Proc.new do
|
15
15
|
option :auto_terminate, type: :boolean, default: false, desc: "automatically terminate the instance at the end of user-data"
|
16
16
|
option :cloudwatch, type: :boolean, desc: "enable cloudwatch logging, supported for amazonlinux2 and ubuntu"
|
17
|
+
option :ami_name, desc: "when specified, an ami creation script is appended to the user-data script"
|
18
|
+
option :randomize, type: :boolean, desc: "append random characters to end of name"
|
19
|
+
option :source_ami, desc: "override the source image_id in profile"
|
20
|
+
option :wait, type: :boolean, default: true, desc: "Wait until the instance is ready and report dns name"
|
21
|
+
option :ssh, type: :boolean, desc: "Ssh into instance immediately after it's ready"
|
22
|
+
option :ssh_user, default: "ec2-user", desc: "User to use to with the ssh option to log into instance"
|
17
23
|
end
|
18
24
|
|
19
25
|
long_desc Help.text(:new)
|
@@ -24,12 +30,6 @@ module Forger
|
|
24
30
|
|
25
31
|
desc "create NAME", "create ec2 instance"
|
26
32
|
long_desc Help.text(:create)
|
27
|
-
option :ami_name, desc: "when specified, an ami creation script is appended to the user-data script"
|
28
|
-
option :randomize, type: :boolean, desc: "append random characters to end of name"
|
29
|
-
option :source_ami, desc: "override the source image_id in profile"
|
30
|
-
option :wait, type: :boolean, default: true, desc: "Wait until the instance is ready and report dns name"
|
31
|
-
option :ssh, type: :boolean, desc: "Ssh into instance immediately after it's ready"
|
32
|
-
option :ssh_user, default: "ec2-user", desc: "User to use to with the ssh option to log into instance"
|
33
33
|
common_options.call
|
34
34
|
def create(name)
|
35
35
|
Create.new(options.merge(name: name)).run
|
@@ -78,5 +78,9 @@ module Forger
|
|
78
78
|
def version
|
79
79
|
puts VERSION
|
80
80
|
end
|
81
|
+
|
82
|
+
desc "s3 SUBCOMMAND", "s3 subcommands"
|
83
|
+
long_desc Help.text(:s3)
|
84
|
+
subcommand "s3", S3
|
81
85
|
end
|
82
86
|
end
|
data/lib/forger/completer.rb
CHANGED
data/lib/forger/core.rb
CHANGED
@@ -3,10 +3,7 @@ require 'yaml'
|
|
3
3
|
|
4
4
|
module Forger
|
5
5
|
module Core
|
6
|
-
|
7
|
-
def config
|
8
|
-
@@config ||= Config.new.data
|
9
|
-
end
|
6
|
+
extend Memoist
|
10
7
|
|
11
8
|
def settings
|
12
9
|
Setting.new.data
|
@@ -44,18 +41,37 @@ module Forger
|
|
44
41
|
Base::BUILD_ROOT
|
45
42
|
end
|
46
43
|
|
44
|
+
# Overrides AWS_PROFILE based on the Forger.env if set in config/settings.yml
|
45
|
+
# 2-way binding.
|
46
|
+
def set_aws_profile!
|
47
|
+
return if ENV['TEST']
|
48
|
+
return unless File.exist?("#{Forger.root}/config/settings.yml") # for rake docs
|
49
|
+
return unless settings # Only load if within Ufo project and there's a settings.yml
|
50
|
+
data = settings[Forger.env] || {}
|
51
|
+
if data["aws_profile"]
|
52
|
+
puts "Using AWS_PROFILE=#{data["aws_profile"]} from FORGER_ENV=#{Forger.env} in config/settings.yml"
|
53
|
+
ENV['AWS_PROFILE'] = data["aws_profile"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Do not use the Setting#data to load the profile because it can cause an
|
58
|
+
# infinite loop then if we decide to use Forger.env from within settings class.
|
59
|
+
def settings
|
60
|
+
path = "#{Forger.root}/config/settings.yml"
|
61
|
+
return {} unless File.exist?(path)
|
62
|
+
YAML.load_file(path)
|
63
|
+
end
|
64
|
+
memoize :settings
|
65
|
+
|
47
66
|
private
|
48
67
|
# Do not use the Setting class to load the profile because it can cause an
|
49
68
|
# infinite loop then if we decide to use Forger.env from within settings class.
|
50
69
|
def env_from_profile(aws_profile)
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
setting ||= {}
|
57
|
-
profiles = setting['aws_profiles']
|
58
|
-
profiles && profiles.include?(aws_profile)
|
70
|
+
return unless settings
|
71
|
+
env = settings.find do |_env, settings|
|
72
|
+
settings ||= {}
|
73
|
+
profiles = settings['aws_profile']
|
74
|
+
profiles && profiles == aws_profile
|
59
75
|
end
|
60
76
|
env.first if env
|
61
77
|
end
|
data/lib/forger/create.rb
CHANGED
@@ -3,12 +3,7 @@ require 'active_support/core_ext/hash'
|
|
3
3
|
|
4
4
|
module Forger
|
5
5
|
class Create < Base
|
6
|
-
|
7
|
-
autoload :ErrorMessages, "forger/create/error_messages"
|
8
|
-
autoload :Waiter, "forger/create/waiter"
|
9
|
-
autoload :Info, "forger/create/info"
|
10
|
-
|
11
|
-
include AwsService
|
6
|
+
include AwsServices
|
12
7
|
include ErrorMessages
|
13
8
|
|
14
9
|
def run
|
@@ -28,7 +23,7 @@ module Forger
|
|
28
23
|
|
29
24
|
instance_id = resp.instances.first.instance_id
|
30
25
|
info.spot(instance_id)
|
31
|
-
puts "EC2 instance #{@name} created: #{instance_id} 🎉"
|
26
|
+
puts "EC2 instance with profile #{@name.color(:green)} created: #{instance_id} 🎉"
|
32
27
|
puts "Visit https://console.aws.amazon.com/ec2/home to check on the status"
|
33
28
|
info.cloudwatch(instance_id)
|
34
29
|
|
@@ -41,23 +36,7 @@ module Forger
|
|
41
36
|
handle_ec2_service_error!(e)
|
42
37
|
end
|
43
38
|
|
44
|
-
# Configured by config/settings.yml.
|
45
|
-
# Example: config/settings.yml:
|
46
|
-
#
|
47
|
-
# Format 1: Simple String
|
48
|
-
#
|
49
|
-
# development:
|
50
|
-
# s3_folder: mybucket/path/to/folder
|
51
|
-
#
|
52
|
-
# Format 2: Hash
|
53
|
-
#
|
54
|
-
# development:
|
55
|
-
# s3_folder:
|
56
|
-
# default: mybucket/path/to/folder
|
57
|
-
# dev_profile1: mybucket/path/to/folder
|
58
|
-
# dev_profile1: another-bucket/storage/path
|
59
39
|
def sync_scripts_to_s3
|
60
|
-
return unless Forger.settings["s3_folder"]
|
61
40
|
upload = Script::Upload.new(@options)
|
62
41
|
return if upload.empty?
|
63
42
|
upload.run
|
data/lib/forger/create/info.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class Forger::Create
|
2
2
|
class Info
|
3
|
-
include Forger::
|
3
|
+
include Forger::AwsServices
|
4
4
|
|
5
5
|
attr_reader :params
|
6
6
|
def initialize(options, params)
|
@@ -16,8 +16,6 @@ class Forger::Create
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def spot(instance_id)
|
19
|
-
puts "Max monthly price: $#{monthly_spot_price}/mo" if monthly_spot_price
|
20
|
-
|
21
19
|
retries = 0
|
22
20
|
begin
|
23
21
|
resp = ec2.describe_instances(instance_ids: [instance_id])
|
@@ -33,7 +31,15 @@ class Forger::Create
|
|
33
31
|
end
|
34
32
|
end
|
35
33
|
|
36
|
-
|
34
|
+
reservation = resp.reservations.first
|
35
|
+
# Super edge case when reserverations not immediately found yet
|
36
|
+
until reservation
|
37
|
+
reservation = resp.reservations.first
|
38
|
+
seconds = 0.5
|
39
|
+
puts "Reserveration not found. Sleeping for #{seconds} and will try again."
|
40
|
+
sleep seconds
|
41
|
+
end
|
42
|
+
spot_id = reservation.instances.first.spot_instance_request_id
|
37
43
|
return unless spot_id
|
38
44
|
|
39
45
|
puts "Spot instance request id: #{spot_id}"
|
data/lib/forger/create/waiter.rb
CHANGED
data/lib/forger/destroy.rb
CHANGED
data/lib/forger/help/upload.md
CHANGED
@@ -2,16 +2,4 @@ Examples:
|
|
2
2
|
|
3
3
|
$ forger upload
|
4
4
|
|
5
|
-
Compiles the app/scripts and app/user_data files to the tmp folder. Then uploads the files to
|
6
|
-
|
7
|
-
```yaml
|
8
|
-
development:
|
9
|
-
# Format 1: Simple String
|
10
|
-
s3_folder: my-bucket/folder # enables auto sync to s3
|
11
|
-
|
12
|
-
# Format 2: Hash
|
13
|
-
# s3_folder:
|
14
|
-
# default: mybucket/path/to/folder
|
15
|
-
# dev_profile1: mybucket/path/to/folder
|
16
|
-
# dev_profile1: another-bucket/storage/path
|
17
|
-
```
|
5
|
+
Compiles the `app/scripts` and `app/user_data` files to the tmp folder. Then uploads the files to the forger managed s3 bucket.
|
data/lib/forger/network.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# If no @vpc_id is provided to the initializer then the default vpc is used.
|
3
3
|
module Forger
|
4
4
|
class Network
|
5
|
-
include Forger::
|
5
|
+
include Forger::AwsServices
|
6
6
|
extend Memoist
|
7
7
|
|
8
8
|
def initialize(vpc_id)
|
@@ -20,7 +20,7 @@ module Forger
|
|
20
20
|
default_vpc.vpc_id
|
21
21
|
else
|
22
22
|
puts "A default vpc was not found in this AWS account and region.".color(:red)
|
23
|
-
puts "Because there is no default vpc, please specify the --vpc-id option.
|
23
|
+
puts "Because there is no default vpc, please specify the --vpc-id option."
|
24
24
|
exit 1
|
25
25
|
end
|
26
26
|
end
|
data/lib/forger/new.rb
CHANGED
@@ -12,17 +12,16 @@ module Forger
|
|
12
12
|
[:git, type: :boolean, default: true, desc: "Git initialize the project"],
|
13
13
|
[:iam, desc: "iam_instance_profile to use in the profiles/default.yml"],
|
14
14
|
[:key_name, desc: "key name to use with launched instance in profiles/default.yml"],
|
15
|
-
[:
|
16
|
-
[:
|
17
|
-
[:
|
18
|
-
[:vpc_id, desc: "Vpc id. For config/development.yml network settings. Will use default sg and subnet"],
|
15
|
+
[:security_group, desc: "Security group to use. For config/variables/development.rb network settings."],
|
16
|
+
[:subnet, desc: "Subnet to use. For config/variables/development.rb network settings."],
|
17
|
+
[:vpc_id, desc: "Vpc id. For config/variables/development.rb network settings. Will use default sg and subnet"],
|
19
18
|
]
|
20
19
|
end
|
21
20
|
|
22
21
|
cli_options.each do |args|
|
23
|
-
class_option
|
22
|
+
class_option(*args)
|
24
23
|
end
|
25
|
-
|
24
|
+
|
26
25
|
def configure_network_settings
|
27
26
|
return if ENV['TEST']
|
28
27
|
|
data/lib/forger/profile.rb
CHANGED
@@ -23,10 +23,23 @@ module Forger
|
|
23
23
|
def load_profile(file)
|
24
24
|
return {} unless File.exist?(file)
|
25
25
|
|
26
|
+
base_file, base_data = profile_file(:base), {}
|
27
|
+
if File.exist?(base_file)
|
28
|
+
puts "Detected profiles/base.yml"
|
29
|
+
base_data = yaml_load(base_file)
|
30
|
+
end
|
31
|
+
|
26
32
|
puts "Using profile: #{file}".color(:green)
|
33
|
+
data = yaml_load(file)
|
34
|
+
data = base_data.merge(data)
|
35
|
+
data.has_key?("run_instances") ? data["run_instances"] : data
|
36
|
+
end
|
37
|
+
|
38
|
+
def yaml_load(file)
|
27
39
|
text = RenderMePretty.result(file, context: context)
|
28
40
|
begin
|
29
|
-
data = YAML.load(text)
|
41
|
+
data = YAML.load(text) # data
|
42
|
+
data ? data : {} # in case the file is empty
|
30
43
|
rescue Psych::SyntaxError => e
|
31
44
|
tmp_file = file.sub("profiles", Forger.build_root)
|
32
45
|
FileUtils.mkdir_p(File.dirname(tmp_file))
|
@@ -36,10 +49,9 @@ module Forger
|
|
36
49
|
puts "ERROR: #{e.message}"
|
37
50
|
exit 1
|
38
51
|
end
|
39
|
-
data ? data : {} # in case the file is empty
|
40
|
-
data.has_key?("run_instances") ? data["run_instances"] : data
|
41
52
|
end
|
42
53
|
|
54
|
+
|
43
55
|
# Determines a valid profile_name. Falls back to default
|
44
56
|
def profile_name
|
45
57
|
# allow user to specify the path also
|
data/lib/forger/s3.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Forger
|
2
|
+
class S3 < Command
|
3
|
+
desc "deploy", "deploys forger managed s3 bucket"
|
4
|
+
long_desc Help.text("s3/deploy")
|
5
|
+
def deploy
|
6
|
+
Bucket.new(options).deploy
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "show", "shows forger managed s3 bucket"
|
10
|
+
long_desc Help.text("s3/show")
|
11
|
+
option :sure, type: :boolean, desc: "Bypass are you sure prompt"
|
12
|
+
def show
|
13
|
+
Bucket.new(options).show
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "delete", "deletes forger managed s3 bucket"
|
17
|
+
long_desc Help.text("s3/delete")
|
18
|
+
option :sure, type: :boolean, desc: "Bypass are you sure prompt"
|
19
|
+
def delete
|
20
|
+
Bucket.new(options).delete
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|