hem 1.0.1.beta6 → 1.1.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +32 -45
  3. data/Hemfile +4 -2
  4. data/README.md +4 -0
  5. data/bin/hem +5 -6
  6. data/hem.gemspec +15 -15
  7. data/lib/hem.rb +47 -45
  8. data/lib/hem/asset_applicators/files.rb +1 -1
  9. data/lib/hem/cli.rb +36 -29
  10. data/lib/hem/error_handlers/debug.rb +1 -1
  11. data/lib/hem/error_handlers/friendly.rb +3 -3
  12. data/lib/hem/errors.rb +23 -12
  13. data/lib/hem/help_formatter.rb +12 -3
  14. data/lib/hem/helper/argument_parser.rb +30 -0
  15. data/lib/hem/helper/command.rb +2 -1
  16. data/lib/hem/helper/file_locator.rb +34 -7
  17. data/lib/hem/helper/shell.rb +0 -1
  18. data/lib/hem/helper/vm_command.rb +2 -2
  19. data/lib/hem/lib/host_check/vagrant.rb +2 -3
  20. data/lib/hem/lib/s3/sync.rb +2 -2
  21. data/lib/hem/lib/seed/project.rb +10 -13
  22. data/lib/hem/lib/seed/replacer.rb +9 -31
  23. data/lib/hem/lib/seed/template.rb +21 -0
  24. data/lib/hem/lib/vm/inspector.rb +1 -1
  25. data/lib/hem/patches/deepstruct.rb +4 -0
  26. data/lib/hem/patches/rake.rb +42 -11
  27. data/lib/hem/plugins.rb +129 -0
  28. data/lib/hem/setup.rb +6 -0
  29. data/lib/hem/tasks.rb +15 -0
  30. data/lib/hem/tasks/deps.rb +13 -14
  31. data/lib/hem/tasks/exec.rb +4 -2
  32. data/lib/hem/tasks/magento.rb +13 -275
  33. data/lib/hem/tasks/mysql.rb +16 -0
  34. data/lib/hem/tasks/ops.rb +2 -1
  35. data/lib/hem/tasks/plugin.rb +16 -0
  36. data/lib/hem/tasks/redis.rb +26 -0
  37. data/lib/hem/tasks/seed.rb +4 -2
  38. data/lib/hem/tasks/self.rb +7 -6
  39. data/lib/hem/tasks/tools.rb +1 -1
  40. data/lib/hem/tasks/vm.rb +8 -16
  41. data/lib/hem/util.rb +6 -16
  42. data/lib/hem/version.rb +12 -1
  43. data/lib/hobo/tasks/magento.rb +2 -1
  44. data/spec/hem/cli_spec.rb +1 -0
  45. data/spec/hem/helpers/argument_parser_spec.rb +57 -0
  46. data/spec/hem/lib/seed/project_spec.rb +1 -0
  47. data/spec/hem/lib/seed/replacer_spec.rb +12 -13
  48. data/spec/hem/lib/seed/seed_spec.rb +1 -0
  49. metadata +50 -42
  50. data/lib/hem/lib/github/api.rb +0 -48
  51. data/lib/hem/lib/github/client.rb +0 -52
  52. data/lib/hem/tasks/pr.rb +0 -45
@@ -3,7 +3,7 @@ module Hem
3
3
  class Debug
4
4
  include Hem::ErrorHandlers::ExitCodeMap
5
5
 
6
- def handle error
6
+ def handle _, error
7
7
  Hem.ui.error "\n(#{error.class}) #{error.message}\n\n#{(error.backtrace || []).join("\n")}"
8
8
  return EXIT_CODES[error.class.to_s] || DEFAULT_EXIT_CODE
9
9
  end
@@ -3,7 +3,7 @@ module Hem
3
3
  class Friendly
4
4
  include Hem::ErrorHandlers::ExitCodeMap
5
5
 
6
- def handle error
6
+ def handle cli, error
7
7
  require 'tmpdir'
8
8
  log_file = File.join(Dir.tmpdir, 'hem_error.log')
9
9
 
@@ -27,10 +27,10 @@ module Hem
27
27
  ERROR
28
28
  when "Hem::InvalidCommandOrOpt"
29
29
  Hem.ui.error "\n#{error.message}"
30
- Hem.ui.info error.cli.help_formatter.help if error.cli
30
+ Hem.ui.info cli.help_formatter.help
31
31
  when "Hem::MissingArgumentsError"
32
32
  Hem.ui.error "\n#{error.message}"
33
- Hem.ui.info error.cli.help_formatter.help(target: error.command) if error.cli
33
+ Hem.ui.info cli.help_formatter.help(target: error.command)
34
34
  when "Hem::UserError"
35
35
  Hem.ui.error "\n#{error.message}\n"
36
36
  when "Hem::ProjectOnlyError"
@@ -2,6 +2,19 @@ module Hem
2
2
  class Error < StandardError
3
3
  attr_reader :exit_code
4
4
  end
5
+
6
+ class HemVersionError < Error
7
+ def initialize requirements
8
+ super("
9
+ This Hem project has specified that it requires the Hem
10
+ version to satisfy the following version requirements:
11
+ #{requirements.join("\n")}
12
+ You are running Hem #{VERSION}, which does not satisfy
13
+ these requirements. Please upgrade Hem to the latest
14
+ version, or relax the Hemfile's version constraints if
15
+ you're already using the latest Hem version.")
16
+ end
17
+ end
5
18
 
6
19
  class RubyVersionError < Error
7
20
  def initialize
@@ -17,24 +30,28 @@ module Hem
17
30
  end
18
31
 
19
32
  class InvalidCommandOrOpt < Error
20
- attr_accessor :command, :cli
21
- def initialize command, cli = nil
33
+ attr_accessor :command
34
+ def initialize command
22
35
  @command = command
23
- @cli = cli
24
36
  super("Invalid command or option specified: '#{command}'")
25
37
  end
26
38
  end
27
39
 
28
40
  class MissingArgumentsError < Error
29
- attr_accessor :command, :cli
30
- def initialize command, args, cli = nil
41
+ attr_accessor :command
42
+ def initialize command, args
31
43
  @command = command
32
44
  @args = args
33
- @cli = cli
34
45
  super("Not enough arguments for #{command}")
35
46
  end
36
47
  end
37
48
 
49
+ class PluginsAlreadySetupError < Error
50
+ def initialize
51
+ super("Hem's plugins have already been set up. Additional plugins can't be defined after.")
52
+ end
53
+ end
54
+
38
55
  class ExternalCommandError < Error
39
56
  attr_accessor :command, :exit_code, :output
40
57
 
@@ -80,10 +97,4 @@ module Hem
80
97
  super('You need to define a preferred editor, either in your hem config or with the EDITOR environment variable')
81
98
  end
82
99
  end
83
-
84
- class GithubAuthenticationError < Error
85
- end
86
-
87
- class GithubApiError < Error
88
- end
89
100
  end
@@ -91,16 +91,25 @@ module Hem
91
91
  def usage source, command = nil
92
92
  banner = source.banner
93
93
  if banner.nil?
94
- arg_list = (source.arg_list || []).map do |arg|
95
- "<#{arg}>"
94
+ arg_list = (source.arg_list || {}).map do |arg, options|
95
+ arg_text = "<#{arg}>"
96
+ if options[:as] == Array
97
+ arg_text = "#{arg_text}..."
98
+ end
99
+ if options[:optional]
100
+ arg_text = "[#{arg_text}]"
101
+ end
102
+ arg_text
96
103
  end
97
104
 
98
105
  banner = "#{File.basename($0, '.*')}"
99
106
  banner << " [command]" if source.commands.any? && command.nil?
100
107
  banner << " #{command.split(':').join(' ')}" if command
101
- banner << " #{arg_list.join(' ')}" if arg_list.size > 0
102
108
  banner << " [options]"
109
+ banner << " #{arg_list.join(' ')}" if arg_list.size > 0
103
110
  end
111
+
112
+ banner
104
113
  end
105
114
 
106
115
  def section title, contents, align_to = false
@@ -0,0 +1,30 @@
1
+ module Hem
2
+ module Helper
3
+ def convert_args task_name, args, arg_list
4
+ original_args = args.dup
5
+ task_args = []
6
+ arg_list.each do |_, options|
7
+ if args.empty?
8
+ if !options[:optional]
9
+ raise ::Hem::MissingArgumentsError.new(task_name, original_args)
10
+ else
11
+ task_args << options[:default]
12
+ end
13
+ elsif options[:as] == Array
14
+ task_args << args.dup
15
+ args.clear
16
+ else
17
+ task_args << args.shift
18
+ end
19
+ end
20
+
21
+ unless args.empty?
22
+ raise ::Hem::InvalidCommandOrOpt.new(args.join(' '))
23
+ end
24
+
25
+ task_args
26
+ end
27
+ end
28
+ end
29
+
30
+ include Hem::Helper
@@ -11,9 +11,10 @@ module Hem
11
11
  end
12
12
  end
13
13
 
14
- def run_command command, opts = {}
14
+ def run command, opts = {}
15
15
  create_command(command, opts).run
16
16
  end
17
+ alias_method :run_command, :run
17
18
 
18
19
  def create_mysql_command opts = {}
19
20
  opts = {
@@ -1,10 +1,27 @@
1
1
  module Hem
2
2
  module Helper
3
- def locate(pattern, opts = {}, &block)
3
+ extend self
4
+
5
+ def locate(name, patterns = nil, opts = {}, &block)
6
+ opts = {
7
+ type: 'git',
8
+ patterns: patterns || [name, "**/#{name}"],
9
+ path: Hem.project_path,
10
+ }.merge(opts)
11
+
4
12
  match = nil
5
13
 
6
- Dir.chdir Hem.project_path do
7
- match = locate_git(pattern, &block)
14
+ unless Hem.project_config[:locate].nil? || Hem.project_config[:locate][name].nil?
15
+ opts = opts.merge(Hem.project_config[:locate][name].to_hash_sym)
16
+ end
17
+
18
+ Dir.chdir opts[:path] do
19
+ case opts[:type]
20
+ when 'git'
21
+ match = locate_git(opts[:patterns], &block)
22
+ when 'files'
23
+ match = locate_files(opts[:patterns], &block)
24
+ end
8
25
  end
9
26
 
10
27
  return match unless block_given?
@@ -16,20 +33,30 @@ module Hem
16
33
 
17
34
  private
18
35
 
19
- def locate_git pattern, &block
20
- args = [ 'git', 'ls-files', pattern ]
36
+ def locate_files patterns, &block
37
+ paths = patterns.inject([]) do |result, pattern|
38
+ result + Dir.glob(pattern)
39
+ end
40
+ locate_loop paths, &block
41
+ end
42
+
43
+ def locate_git patterns, &block
44
+ args = [ 'git', 'ls-files', *patterns ]
21
45
  output = Hem::Helper.shell *args, :capture => true
22
46
  paths = output.split("\n")
23
- found = false
24
47
  paths.each do |path|
25
48
  path.strip!
26
49
  end
50
+ locate_loop paths, &block
51
+ end
27
52
 
53
+ def locate_loop paths, &block
28
54
  return paths unless block_given?
29
55
 
56
+ found = false
30
57
  paths.each do |path|
31
58
  Dir.chdir File.dirname(path) do
32
- Hem::Logging.logger.debug "helper.locator: Found #{path} for #{pattern}"
59
+ Hem::Logging.logger.debug "helper.locator: Found #{path}"
33
60
  yield File.basename(path), path
34
61
  end
35
62
 
@@ -49,7 +49,6 @@ module Hem
49
49
 
50
50
  require 'bundler'
51
51
  ::Bundler.with_clean_env do
52
- Hem.chefdk_compat
53
52
  indent = " " * opts[:indent]
54
53
  ::Open3.popen3 opts[:env], *args do |stdin, out, err, external|
55
54
  buffer = ::Tempfile.new 'hem_run_buf'
@@ -1,9 +1,9 @@
1
1
  module Hem
2
2
  module Helper
3
3
  def vm_shell command, opts = {}
4
- Hem.ui.warning "Using vm_shell is deprecated and will be removed in a future release. Please use run_command instead"
4
+ Hem.ui.warning "Using vm_shell is deprecated and will be removed in a future release. Please use run instead"
5
5
  opts['run_environment'] = 'vm'
6
- run_command command, opts
6
+ run command, opts
7
7
  end
8
8
 
9
9
  def vm_mysql opts = {}
@@ -2,14 +2,13 @@ module Hem
2
2
  module Lib
3
3
  module HostCheck
4
4
  def vagrant_version opts
5
- require 'semantic'
6
5
  begin
7
6
  return unless get_run_environment == 'vm'
8
7
 
9
8
  version = shell "vagrant --version", :capture => true
10
9
  version.gsub!(/^Vagrant[^0-9]+/, '')
11
- version = ::Semantic::Version.new version.strip
12
- minimum_version = ::Semantic::Version.new "1.3.5"
10
+ version = Gem::Version.new(version.strip)
11
+ minimum_version = Gem::Version.new("1.3.5")
13
12
 
14
13
  advice = <<-EOF
15
14
  The version of vagrant which you are using (#{version}) is less than the minimum required (#{minimum_version}).
@@ -101,11 +101,11 @@ module Hem
101
101
  # We allow this one to be skipped as there are obviously no assets to sync
102
102
  rescue Aws::S3::Errors::AccessDenied
103
103
  Hem.ui.error " Your AWS key does not have access to the #{Hem.project_config.asset_bucket} S3 bucket!"
104
- Hem.ui.error " Please request access to this bucket from your TTL or via an internal support request"
104
+ Hem.ui.error " Please request access to this bucket from your Amazon AWS account administrators"
105
105
  raise exception
106
106
  rescue Aws::Errors::MissingCredentialsError
107
107
  Hem.ui.warning " AWS credentials not set!"
108
- Hem.ui.warning " Please request credentials from internalsupport@inviqa.com or in #devops and configure them with `hem config`"
108
+ Hem.ui.warning " Please request credentials from your Amazon AWS account administrators and configure them with `hem config`"
109
109
  raise exception
110
110
  end
111
111
  end
@@ -6,8 +6,8 @@ module Hem
6
6
  @opts = {
7
7
  :replacer => Replacer.new,
8
8
  :config_class => Hem::Config::File,
9
- :ssl_cert_generator => Hem::Lib::SelfSignedCertGenerator
10
9
  }.merge! opts
10
+ @replace_done = false
11
11
  end
12
12
 
13
13
  def setup seed, config
@@ -17,27 +17,24 @@ module Hem
17
17
  seed.export project_path, config
18
18
 
19
19
  Dir.chdir(project_path) do
20
- Hem.detect_project_type project_path
21
- @opts[:project_config_file] = Hem.project_config_file
22
-
23
20
  config[:seed][:version] = seed.version
24
21
  config[:hostname] = "#{config[:name]}.dev"
25
22
  config[:asset_bucket] = "inviqa-assets-#{config[:name]}"
26
23
  config[:vm] = {
27
24
  :project_mount_path => "/vagrant"
28
25
  }
29
- config[:ssl] = @opts[:ssl_cert_generator].generate config[:hostname]
30
- config[:chef_ssl] = {}
31
- config[:ssl].each do |k, v|
32
- config[:chef_ssl][k] = v.gsub("\n", "\\n")
33
- end
26
+ config[:tmp] = {}
34
27
 
35
- @opts[:replacer].replace(config[:project_path], config)
28
+ Hem.project_path = project_path
36
29
  load_seed_init(config)
37
30
 
31
+ @opts[:replacer].replace(config[:project_path], Hem.project_config)
32
+
38
33
  config.delete :project_path
39
- config.delete :ssl
40
- config.delete :chef_ssl
34
+ config.delete :tmp
35
+
36
+ Hem.detect_project_type project_path
37
+ @opts[:project_config_file] = Hem.project_config_file
41
38
  @opts[:config_class].save @opts[:project_config_file], config
42
39
  end
43
40
 
@@ -50,7 +47,7 @@ module Hem
50
47
  Hem.project_config = DeepStruct.wrap(config)
51
48
  seed_init_file = File.join(config[:project_path], 'seedinit.rb')
52
49
  if File.exists?(seed_init_file)
53
- require seed_init_file
50
+ instance_eval File.read(seed_init_file), seed_init_file
54
51
  File.unlink(seed_init_file)
55
52
  end
56
53
  end
@@ -2,14 +2,9 @@ module Hem
2
2
  module Lib
3
3
  module Seed
4
4
  class Replacer
5
- # Matching files/directories to be excluded from replacements
6
- EXCLUDES = ["\\.git/", "^./bin", "^./lib", "^./spec"]
7
-
8
5
  def replace(path, tokens)
9
- if tokens.instance_of? Hash
10
- tokens = flat_hash(tokens)
11
- elsif !tokens.instance_of? Array
12
- raise "Invalid token list (expected Array or Hash)"
6
+ unless !tokens.instance_of?(Hash)
7
+ raise "Invalid token list (expected Hash)"
13
8
  end
14
9
 
15
10
  return search_replace(path, tokens)
@@ -18,36 +13,19 @@ module Hem
18
13
  private
19
14
 
20
15
  def search_replace(path, tokens, &block)
21
- require 'find'
22
- files = []
23
- excludes = Regexp.new(EXCLUDES.join("|"))
24
- Find.find(path) do |candidate|
25
- Find.prune if candidate =~ excludes # Skip excluded
16
+ require 'erb'
17
+
18
+ template = Template.new(tokens)
19
+ Hem::Helper.locate('*.erb', nil, path: path, type: 'files').each do |candidate|
26
20
  next unless FileTest.file? candidate # Skip unless file
27
21
 
28
22
  content = File.read(candidate)
29
23
  next unless content.force_encoding("UTF-8").valid_encoding? # Skip unless file can be valid UTF-8
30
24
 
31
- match = false
32
- tokens.each do |token, replacement|
33
- token = "{{#{token.join('.')}}}"
34
- match = content.match(token)
35
- if match
36
- content.gsub!(token, replacement)
37
- files.push(candidate)
38
- end
39
- end
40
-
41
- File.write(candidate, content) if files.include? candidate
42
- end
43
- return files.uniq
44
- end
25
+ content = template.render(content, candidate)
45
26
 
46
- # http://stackoverflow.com/questions/9647997/converting-a-nested-hash-into-a-flat-hash
47
- def flat_hash(hash, k = [])
48
- return {k => hash} unless hash.is_a?(Hash)
49
- hash.inject({}) do |h, v|
50
- h.merge! flat_hash(v[-1], k + [v[0]])
27
+ File.delete(candidate)
28
+ File.write(candidate.sub('.erb', ''), content)
51
29
  end
52
30
  end
53
31
  end
@@ -0,0 +1,21 @@
1
+ module Hem
2
+ module Lib
3
+ module Seed
4
+ class Template
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def config
10
+ @config
11
+ end
12
+
13
+ def render(content, filename)
14
+ erb = ERB.new(content)
15
+ erb.filename = filename
16
+ erb.result binding
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -45,7 +45,7 @@ module Hem
45
45
  def ssh_config
46
46
  return @ssh_config if @ssh_config
47
47
  config = nil
48
- locate "*Vagrantfile" do
48
+ locate "Vagrantfile" do |_, file|
49
49
  config = shell "vagrant ssh-config", :capture => true
50
50
  end
51
51
 
@@ -17,5 +17,9 @@ module DeepStruct
17
17
  Hem::Null.new
18
18
  end
19
19
  end
20
+
21
+ def to_hash_sym
22
+ unwrap.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
23
+ end
20
24
  end
21
25
  end