hem 1.0.1.beta6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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