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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -0
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +40 -29
  5. data/README.md +6 -37
  6. data/docs/example/config/variables/development.rb +2 -0
  7. data/docs/example/profiles/default.yml +2 -2
  8. data/docs/extract-scripts.md +40 -0
  9. data/docs/layouts.md +35 -0
  10. data/docs/profiles.md +79 -0
  11. data/docs/variables.md +53 -0
  12. data/forger.gemspec +6 -3
  13. data/lib/forger.rb +4 -23
  14. data/lib/forger/autoloader.rb +21 -0
  15. data/lib/forger/aws_services.rb +22 -0
  16. data/lib/forger/clean.rb +0 -2
  17. data/lib/forger/cleaner.rb +0 -1
  18. data/lib/forger/cleaner/ami.rb +1 -1
  19. data/lib/forger/cli.rb +10 -6
  20. data/lib/forger/completer.rb +0 -2
  21. data/lib/forger/core.rb +28 -12
  22. data/lib/forger/create.rb +2 -23
  23. data/lib/forger/create/info.rb +10 -4
  24. data/lib/forger/create/waiter.rb +1 -1
  25. data/lib/forger/destroy.rb +1 -1
  26. data/lib/forger/help/upload.md +1 -13
  27. data/lib/forger/network.rb +2 -2
  28. data/lib/forger/new.rb +5 -6
  29. data/lib/forger/profile.rb +15 -3
  30. data/lib/forger/s3.rb +23 -0
  31. data/lib/forger/s3/bucket.rb +131 -0
  32. data/lib/forger/script.rb +0 -4
  33. data/lib/forger/script/upload.rb +12 -42
  34. data/lib/forger/scripts/cloudwatch.sh +2 -2
  35. data/lib/forger/scripts/shared/functions.sh +1 -1
  36. data/lib/forger/setting.rb +0 -32
  37. data/lib/forger/template.rb +0 -3
  38. data/lib/forger/template/context.rb +16 -1
  39. data/lib/forger/template/helper.rb +9 -9
  40. data/lib/forger/template/helper/ami_helper.rb +1 -1
  41. data/lib/forger/template/helper/core_helper.rb +7 -6
  42. data/lib/forger/template/helper/script_helper.rb +3 -9
  43. data/lib/forger/version.rb +1 -1
  44. data/lib/forger/wait.rb +0 -2
  45. data/lib/forger/waiter.rb +0 -1
  46. data/lib/forger/waiter/ami.rb +1 -1
  47. data/lib/templates/default/app/user_data/bootstrap.sh.tt +6 -9
  48. data/lib/templates/default/app/user_data/layouts/default.sh.tt +1 -5
  49. data/lib/templates/default/config/settings.yml.tt +1 -11
  50. data/lib/templates/default/config/{development.yml.tt → variables/development.yml.tt} +0 -0
  51. data/spec/fixtures/demo_project/app/user_data/bootstrap.sh +2 -2
  52. data/spec/fixtures/demo_project/config/settings.yml +2 -4
  53. data/spec/fixtures/demo_project/config/variables/test.rb +4 -0
  54. data/spec/fixtures/demo_project/profiles/default.yml +2 -2
  55. metadata +59 -13
  56. data/docs/example/config/development.yml +0 -7
  57. data/lib/forger/aws_service.rb +0 -7
  58. data/lib/forger/config.rb +0 -25
  59. data/spec/fixtures/demo_project/config/test.yml +0 -8
@@ -0,0 +1,131 @@
1
+ require "cfn/status"
2
+
3
+ class Forger::S3
4
+ class Bucket
5
+ STACK_NAME = ENV['FORGER_STACK_NAME'] || "forger"
6
+ include Forger::AwsServices
7
+ extend Forger::AwsServices
8
+
9
+ class << self
10
+ @@name = nil
11
+ def name
12
+ return @@name if @@name # only memoize once bucket has been created
13
+
14
+ stack = new.find_stack
15
+ return unless stack
16
+
17
+ resp = cfn.describe_stack_resources(stack_name: STACK_NAME)
18
+ bucket = resp.stack_resources.find { |r| r.logical_resource_id == "Bucket" }
19
+ @@name = bucket.physical_resource_id # actual bucket name
20
+ end
21
+
22
+ def ensure_exists!
23
+ bucket = new
24
+ return if bucket.exist?
25
+ bucket.create
26
+ end
27
+ end
28
+
29
+ def initialize(options={})
30
+ @options = options
31
+ end
32
+
33
+ def deploy
34
+ stack = find_stack
35
+ if stack
36
+ update
37
+ else
38
+ create
39
+ end
40
+ end
41
+
42
+ def exist?
43
+ !!bucket_name
44
+ end
45
+
46
+ def bucket_name
47
+ self.class.name
48
+ end
49
+
50
+ def show
51
+ if bucket_name
52
+ puts "Forger bucket name: #{bucket_name}"
53
+ else
54
+ puts "Forger bucket does not exist yet."
55
+ end
56
+ end
57
+
58
+ # Launches a cloudformation to create an s3 bucket
59
+ def create
60
+ puts "Creating #{STACK_NAME} stack with the s3 bucket"
61
+ cfn.create_stack(stack_name: STACK_NAME, template_body: template_body)
62
+ status = ::Cfn::Status.new(STACK_NAME)
63
+ status.wait
64
+ end
65
+
66
+ def update
67
+ puts "Updating #{STACK_NAME} stack with the s3 bucket"
68
+ cfn.update_stack(stack_name: STACK_NAME, template_body: template_body)
69
+ rescue Aws::CloudFormation::Errors::ValidationError => e
70
+ raise unless e.message.include?("No updates are to be performed")
71
+ end
72
+
73
+ def delete
74
+ are_you_sure?
75
+
76
+ puts "Deleting #{STACK_NAME} stack with the s3 bucket"
77
+ empty_bucket!
78
+ cfn.delete_stack(stack_name: STACK_NAME)
79
+ end
80
+
81
+ def find_stack
82
+ resp = cfn.describe_stacks(stack_name: STACK_NAME)
83
+ resp.stacks.first
84
+ rescue Aws::CloudFormation::Errors::ValidationError
85
+ nil
86
+ end
87
+
88
+ private
89
+
90
+ def empty_bucket!
91
+ resp = s3.list_objects(bucket: bucket_name)
92
+ if resp.contents.size > 0
93
+ # IE: objects = [{key: "objectkey1"}, {key: "objectkey2"}]
94
+ objects = resp.contents.map { |item| {key: item.key} }
95
+ s3.delete_objects(
96
+ bucket: bucket_name,
97
+ delete: {
98
+ objects: objects,
99
+ quiet: false,
100
+ }
101
+ )
102
+ empty_bucket! # keep deleting objects until bucket is empty
103
+ end
104
+ end
105
+
106
+
107
+ def are_you_sure?
108
+ return true if @options[:sure]
109
+
110
+ puts "Are you sure you want the forger bucket #{bucket_name.color(:green)} to be emptied and deleted? (y/N)"
111
+ sure = $stdin.gets.strip
112
+ yes = sure =~ /^Y/i
113
+ unless yes
114
+ puts "Phew that was close."
115
+ exit
116
+ end
117
+ end
118
+
119
+ def template_body
120
+ <<~YAML
121
+ Resources:
122
+ Bucket:
123
+ Type: AWS::S3::Bucket
124
+ Properties:
125
+ Tags:
126
+ - Key: Name
127
+ Value: forger
128
+ YAML
129
+ end
130
+ end
131
+ end
@@ -1,9 +1,5 @@
1
1
  module Forger
2
2
  class Script
3
- autoload :Compile, "forger/script/compile"
4
- autoload :Compress, "forger/script/compress"
5
- autoload :Upload, "forger/script/upload"
6
-
7
3
  def initialize(options={})
8
4
  @options = options
9
5
  end
@@ -15,12 +15,17 @@ class Forger::Script
15
15
 
16
16
  def run
17
17
  compiler.compile_scripts if @compile
18
+ ensure_bucket_exists
18
19
  compressor.compress
19
20
  upload(tarball_path)
20
21
  compressor.clean
21
22
  compiler.clean if @compile and Forger.settings["compile_clean"]
22
23
  end
23
24
 
25
+ def ensure_bucket_exists
26
+ Forger::S3::Bucket.ensure_exists! if Forger::Template::Helper.extract_scripts_registered?
27
+ end
28
+
24
29
  def upload(tarball_path)
25
30
  if @options[:noop]
26
31
  puts "NOOP: Not uploading file to s3"
@@ -28,32 +33,13 @@ class Forger::Script
28
33
  end
29
34
 
30
35
  puts "Uploading scripts.tgz (#{filesize}) to #{s3_dest}".color(:green)
31
- obj = s3_resource.bucket(bucket_name).object(key)
36
+ obj = s3_resource.bucket(bucket_name).object(s3_key)
32
37
  start_time = Time.now
33
- upload_to_s3(obj, tarball_path)
38
+ obj.upload_file(tarball_path)
34
39
  time_took = pretty_time(Time.now-start_time).color(:green)
35
40
  puts "Time to upload code to s3: #{time_took}"
36
41
  end
37
42
 
38
- def upload_to_s3(obj, tarball_path)
39
- obj.upload_file(tarball_path)
40
- rescue Aws::S3::Errors::PermanentRedirect => e
41
- puts "ERROR: #{e.class} #{e.message}".color(:red)
42
- puts "The bucket you are trying to upload scripts to is in a different region than the region the instance is being launched in."
43
- puts "You must configured FORGER_S3_ENDPOINT env variable to prevent this error. Example:"
44
- puts " FORGER_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com"
45
- puts "Check your ~/.aws/config for the region being used for the ec2 instance."
46
- exit 1
47
- rescue Aws::S3::Errors::AccessDenied, Aws::S3::Errors::AllAccessDisabled
48
- e = $!
49
- puts "ERROR: #{e.class} #{e.message}".color(:red)
50
- puts "You do not have permission to upload scripts to this bucket: #{bucket_name}. Are you sure the right bucket is configured?"
51
- if ENV['AWS_PROFILE']
52
- puts "Also maybe check your AWS_PROFILE env. Current AWS_PROFILE=#{ENV['AWS_PROFILE']}"
53
- end
54
- exit 1
55
- end
56
-
57
43
  def empty?
58
44
  Dir.glob("#{Forger.root}/app/scripts/**/*").select do |path|
59
45
  File.file?(path)
@@ -69,33 +55,17 @@ class Forger::Script
69
55
  end
70
56
 
71
57
  def s3_dest
72
- "s3://#{bucket_name}/#{key}"
58
+ "s3://#{bucket_name}/#{s3_key}"
73
59
  end
74
60
 
75
- def key
76
- # Example key: ec2/development/scripts/scripts-md5
61
+ def s3_key
62
+ # Example s3_key: ec2/development/scripts/scripts-md5
63
+ dest_folder = "#{Forger.env}/scripts"
77
64
  "#{dest_folder}/#{File.basename(tarball_path)}"
78
65
  end
79
66
 
80
- # Example:
81
- # s3_folder: s3://infra-bucket/ec2
82
- # bucket_name: infra-bucket
83
67
  def bucket_name
84
- s3_folder.sub('s3://','').split('/').first
85
- end
86
-
87
- # Removes s3://bucket-name and adds Forger.env. Example:
88
- # s3_folder: s3://infra-bucket/ec2
89
- # bucket_name: ec2/development/scripts
90
- def dest_folder
91
- folder = s3_folder.sub('s3://','').split('/')[1..-1].join('/')
92
- folder = nil if folder == ''
93
- [folder, "#{Forger.env}/scripts"].compact.join('/')
94
- end
95
-
96
- # s3_folder example:
97
- def s3_folder
98
- Forger.settings["s3_folder"]
68
+ Forger::S3::Bucket.name
99
69
  end
100
70
 
101
71
  def s3_resource
@@ -13,8 +13,8 @@ LOG_GROUP_NAME=$1
13
13
  # shellcheck disable=SC1091
14
14
  source "/opt/forger/shared/functions.sh"
15
15
  OS=$(os_name)
16
- if [ "$OS" != "amzn" ] && [ "$OS" != "amazonlinux2" ] && [ "$OS" != "ubuntu" ] ; then
17
- echo "Sorry, cloudwatch logging with the forger tool is supported for amazonlinux2 and ubuntu only"
16
+ if [ "$OS" != "amzn" ] && [ "$OS" != "amzn2" ] && [ "$OS" != "ubuntu" ] ; then
17
+ echo "Sorry, cloudwatch logging with the forger tool is supported for amzn2 and ubuntu only"
18
18
  exit
19
19
  fi
20
20
 
@@ -63,7 +63,7 @@ function os_name() {
63
63
  OS="${OS#\"}" # remove leading "
64
64
  OS="${OS%\"}" # remove trailing "
65
65
 
66
- if [ "$OS" == "amazonlinux" ]; then
66
+ if [ "$OS" == "amzn" ]; then
67
67
  VERSION=$(gawk -F= '/^VERSION/{print $2}' /etc/os-release)
68
68
  VERSION="${VERSION#\"}" # remove leading "
69
69
  if [[ "$VERSION" =~ ^2 ]] ; then
@@ -20,41 +20,9 @@ module Forger
20
20
  all_envs = load_file(project_settings_path)
21
21
  all_envs = merge_base(all_envs)
22
22
  @@data = all_envs[Forger.env] || all_envs["base"] || {}
23
- @@data = flatten_s3_folder(@@data)
24
23
  @@data
25
24
  end
26
25
 
27
- # So we can access settings['s3_folder'] transparently
28
- def flatten_s3_folder(data)
29
- # data = data.clone
30
- if data['s3_folder'].is_a?(Hash)
31
- data['s3_folder'] = s3_folder
32
- end
33
- data
34
- end
35
-
36
- # Special helper method to support multiple formats for s3_folder setting.
37
- # Format 1: Simple String
38
- #
39
- # development:
40
- # s3_folder: mybucket/path/to/folder
41
- #
42
- # Format 2: Hash
43
- #
44
- # development:
45
- # s3_folder:
46
- # default: mybucket/path/to/folder
47
- # dev_profile1: mybucket/path/to/folder
48
- # dev_profile1: another-bucket/storage/path
49
- #
50
- def s3_folder
51
- s3_folder = data['s3_folder']
52
- return s3_folder if s3_folder.nil? or s3_folder.is_a?(String)
53
-
54
- # If reach here then the s3_folder is a Hash
55
- s3_folder[ENV['AWS_PROFILE']] || s3_folder["default"]
56
- end
57
-
58
26
  private
59
27
  def load_file(path)
60
28
  return Hash.new({}) unless File.exist?(path)
@@ -3,9 +3,6 @@ require "active_support/core_ext/string"
3
3
 
4
4
  module Forger
5
5
  module Template
6
- autoload :Context, "forger/template/context"
7
- autoload :Helper, "forger/template/helper"
8
-
9
6
  def context
10
7
  @context ||= Forger::Template::Context.new(@options)
11
8
  end
@@ -8,15 +8,30 @@ module Forger::Template
8
8
 
9
9
  def initialize(options={})
10
10
  @options = options
11
+ load_variables
11
12
  load_custom_helpers
12
13
  end
13
14
 
14
15
  private
16
+ # Load variables from:
17
+ # config/variables/development.rb
18
+ # config/variables/production.rb
19
+ # etc
20
+ def load_variables
21
+ load_variables_file(:base)
22
+ load_variables_file(Forger.env)
23
+ end
24
+
25
+ def load_variables_file(type)
26
+ path = "#{Forger.root}/config/variables/#{type}.rb"
27
+ instance_eval(IO.read(path), path) if File.exist?(path)
28
+ end
29
+
15
30
  # Load custom helper methods from project
16
31
  def load_custom_helpers
17
32
  Dir.glob("#{Forger.root}/app/helpers/**/*_helper.rb").each do |path|
18
33
  filename = path.sub(%r{.*/},'').sub('.rb','')
19
- module_name = filename.classify
34
+ module_name = filename.camelize
20
35
 
21
36
  # Prepend a period so require works FORGER_ROOT is set to a relative path
22
37
  # without a period.
@@ -2,16 +2,16 @@ require "active_support/core_ext/string"
2
2
 
3
3
  module Forger::Template
4
4
  module Helper
5
- def autoinclude(klass)
6
- autoload klass, "forger/template/helper/#{klass.to_s.underscore}"
7
- include const_get(klass)
8
- end
5
+ include AmiHelper
6
+ include CoreHelper
7
+ include PartialHelper
8
+ include ScriptHelper
9
+ include SshKeyHelper
9
10
  extend self
10
11
 
11
- autoinclude :AmiHelper
12
- autoinclude :CoreHelper
13
- autoinclude :PartialHelper
14
- autoinclude :ScriptHelper
15
- autoinclude :SshKeyHelper
12
+ @@extract_scripts_registered = false
13
+ def extract_scripts_registered?
14
+ @@extract_scripts_registered
15
+ end
16
16
  end
17
17
  end
@@ -1,5 +1,5 @@
1
1
  module Forger::Template::Helper::AmiHelper
2
- include Forger::AwsService
2
+ include Forger::AwsServices
3
3
 
4
4
  # Example:
5
5
  #
@@ -10,7 +10,7 @@ module Forger::Template::Helper::CoreHelper
10
10
  path = "#{Forger.root}/app/user_data/#{name}"
11
11
  unless File.exist?(path)
12
12
  puts "ERROR: user-data script #{path.color(:red)} does not exist"
13
- exit
13
+ exit 1
14
14
  end
15
15
  result = RenderMePretty.result(path, context: self, layout: layout_path)
16
16
  # Must prepend and append scripts in user_data here because we need to
@@ -58,11 +58,12 @@ module Forger::Template::Helper::CoreHelper
58
58
  end
59
59
 
60
60
  # provides access to config/* settings as variables
61
- # FORGER_ENV=development => config/development.yml
62
- # FORGER_ENV=production => config/production.yml
63
- def config
64
- Forger.config
61
+ # FORGER_ENV=development => config/variables/development.rb
62
+ # FORGER_ENV=production => config/variables/production.rb
63
+ def vars
64
+ Forger.vars
65
65
  end
66
+ alias_method :variables, :vars
66
67
 
67
68
  # provides access to config/settings.yml as variables
68
69
  def settings
@@ -122,7 +123,7 @@ private
122
123
  def load_custom_helpers
123
124
  Dir.glob("#{Forger.root}/app/helpers/**/*_helper.rb").each do |path|
124
125
  filename = path.sub(%r{.*/},'').sub('.rb','')
125
- module_name = filename.classify
126
+ module_name = filename.camelize
126
127
 
127
128
  require path
128
129
  self.class.send :include, module_name.constantize
@@ -1,7 +1,7 @@
1
1
  module Forger::Template::Helper::ScriptHelper
2
2
  # Bash code that is meant to included in user-data
3
3
  def extract_scripts(options={})
4
- check_s3_folder_settings!
4
+ @@extract_scripts_registered = true
5
5
 
6
6
  settings_options = settings["extract_scripts"] || {}
7
7
  options = settings_options.merge(options)
@@ -23,6 +23,7 @@ mkdir -p #{to}
23
23
  aws s3 cp #{scripts_s3_path} #{to}/
24
24
  (
25
25
  cd #{to}
26
+ rm -rf #{to}/scripts # cleanup
26
27
  tar zxf #{to}/#{scripts_name}
27
28
  chmod -R a+x #{to}/scripts
28
29
  chown -R #{user}:#{user} #{to}/scripts
@@ -31,15 +32,8 @@ BASH_CODE
31
32
  end
32
33
 
33
34
  private
34
- def check_s3_folder_settings!
35
- return if settings["s3_folder"]
35
+ def register_extract_scripts
36
36
 
37
- puts "The extract_scripts helper method aws called. It requires the s3_folder to be set at:"
38
- lines = caller.reject { |l| l =~ %r{lib/forger} } # hide internal forger trace
39
- puts " #{lines[0]}"
40
-
41
- puts "Please configure your config/settings.yml with an s3_folder.".color(:red)
42
- exit 1
43
37
  end
44
38
 
45
39
  def scripts_name
@@ -1,3 +1,3 @@
1
1
  module Forger
2
- VERSION = "2.0.5"
2
+ VERSION = "3.0.0"
3
3
  end