vendorificator 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.travis.yml +5 -2
  2. data/Gemfile +0 -1
  3. data/README.md +9 -0
  4. data/Rakefile +7 -1
  5. data/features/chef_cookbook.feature +4 -6
  6. data/features/deprecated.feature +4 -4
  7. data/features/git.feature +41 -2
  8. data/features/needed.feature +8 -7
  9. data/features/smoke.feature +7 -5
  10. data/features/status.feature +15 -15
  11. data/features/step_definitions/aruba_ext.rb +11 -0
  12. data/features/step_definitions/basic.rb +16 -43
  13. data/features/step_definitions/git.rb +11 -11
  14. data/features/step_definitions/vendorificator.rb +4 -6
  15. data/features/support/aruba_ext.rb +23 -0
  16. data/features/support/env.rb +13 -20
  17. data/features/support/minigit.rb +37 -0
  18. data/features/tarball.feature +7 -7
  19. data/features/tarball_edit.feature +1 -1
  20. data/features/vendor.feature +2 -2
  21. data/lib/vendorificator/cli.rb +36 -61
  22. data/lib/vendorificator/config.rb +5 -42
  23. data/lib/vendorificator/environment.rb +111 -0
  24. data/lib/vendorificator/hooks/chef_cookbook.rb +59 -23
  25. data/lib/vendorificator/vendor/archive.rb +2 -2
  26. data/lib/vendorificator/vendor/chef_cookbook.rb +2 -2
  27. data/lib/vendorificator/vendor/download.rb +44 -0
  28. data/lib/vendorificator/vendor/git.rb +9 -11
  29. data/lib/vendorificator/vendor.rb +105 -48
  30. data/lib/vendorificator/version.rb +1 -1
  31. data/lib/vendorificator.rb +2 -0
  32. data/spec/spec_helper.rb +1 -0
  33. data/spec/vendorificator/vendor_spec.rb +15 -7
  34. data/vendorificator.gemspec +4 -4
  35. metadata +24 -21
  36. data/features/support/world_git.rb +0 -40
  37. data/features/support/world_runs.rb +0 -93
  38. data/lib/vendorificator/repo.rb +0 -69
@@ -4,25 +4,38 @@ require 'vendorificator'
4
4
 
5
5
  module Vendorificator
6
6
  class CLI < Thor
7
- include Vendorificator
7
+ attr_reader :environment
8
8
 
9
9
  check_unknown_options! :except => [:git, :diff, :log]
10
10
  stop_on_unknown_option! :git, :diff, :log
11
11
 
12
12
  default_task :help
13
13
 
14
- class_option :file, :aliases => '-f', :type => :string, :banner => 'PATH'
15
- class_option :debug, :aliases => '-d', :type => :boolean, :default => false
16
- class_option :quiet, :aliases => ['-q'], :default => false, :type => :boolean
17
- class_option :modules, :type => :string, :default => '',
14
+ class_option :file, :aliases => '-f', :type => :string,
15
+ :banner => 'PATH'
16
+ class_option :debug, :aliases => '-d', :type => :boolean, :default => false
17
+ class_option :quiet, :aliases => '-q', :type => :boolean, :default => false
18
+ class_option :modules, :aliases => '-m', :type => :string, :default => '',
18
19
  :banner => 'mod1,mod2,...,modN',
19
20
  :desc => 'Run only for specified modules (name or path, comma separated)'
21
+ class_option :version, :type => :boolean
22
+ class_option :help, :aliases => '-h', :type => :boolean
20
23
 
21
- def initialize(*args)
24
+ def initialize(args=[], options={}, config={})
22
25
  super
23
- Grit.debug = true if options[:debug]
24
- Vendorificator::Config.from_file(find_vendorfile)
25
- Vendorificator::Config[:shell] = shell
26
+
27
+ if self.options[:version]
28
+ say "Vendorificator #{Vendorificator::VERSION}"
29
+ exit
30
+ end
31
+
32
+ if self.options[:help] && config[:current_task].name != 'help'
33
+ invoke :help, [ config[:current_task].name ]
34
+ exit
35
+ end
36
+
37
+ @environment = Vendorificator::Environment.new(self.options[:file])
38
+ environment.shell = shell
26
39
 
27
40
  class << shell
28
41
  # Make say_status always say it.
@@ -35,9 +48,9 @@ module Vendorificator
35
48
  desc :sync, "Download new or updated vendor files"
36
49
  method_option :update, :type => :boolean, :default => false
37
50
  def sync
38
- ensure_clean_repo!
39
- conf[:use_upstream_version] = options[:update]
40
- Vendorificator::Config.each_module(*modules) do |mod|
51
+ ensure_clean!
52
+ environment.config[:use_upstream_version] = options[:update]
53
+ Vendorificator::Vendor.each(*modules) do |mod|
41
54
  say_status :module, mod.name
42
55
  begin
43
56
  shell.padding += 1
@@ -51,9 +64,9 @@ module Vendorificator
51
64
  desc "status", "List known vendor modules and their status"
52
65
  method_option :update, :type => :boolean, :default => false
53
66
  def status
54
- say_status 'WARNING', 'Git repository is not clean', :red unless repo.clean?
55
- conf[:use_upstream_version] = options[:update]
56
- Vendorificator::Config.each_module(*modules) do |mod|
67
+ say_status 'WARNING', 'Git repository is not clean', :red unless environment.clean?
68
+ environment.config[:use_upstream_version] = options[:update]
69
+ Vendorificator::Vendor.each(*modules) do |mod|
57
70
  status_line = mod.to_s
58
71
 
59
72
  updatable = mod.updatable?
@@ -74,11 +87,11 @@ module Vendorificator
74
87
  method_option :remote, :aliases => ['-r'], :default => nil
75
88
  method_option :dry_run, :aliases => ['-n'], :default => false, :type => :boolean
76
89
  def pull
77
- ensure_clean_repo!
78
- remotes = options[:remote] ? options[:remote].split(',') : conf[:remotes]
90
+ ensure_clean!
91
+ remotes = options[:remote] ? options[:remote].split(',') : environment.config[:remotes]
79
92
  remotes.each do |remote|
80
93
  indent 'remote', remote do
81
- repo.pull(remote, options)
94
+ environment.pull(remote, options)
82
95
  end
83
96
  end
84
97
  end
@@ -96,9 +109,8 @@ module Vendorificator
96
109
  vendor git log @MERGED@..HEAD -- @PATH@ # basic 'vendor log'
97
110
  vendor git diff --stat @MERGED@ -- @PATH@ # 'vendor diff', as diffstat
98
111
  EOF
99
- method_option :only_changed, :default => false, :type => :boolean
100
112
  def git(command, *args)
101
- Vendorificator::Config.each_module(*modules) do |mod|
113
+ Vendorificator::Vendor.each(*modules) do |mod|
102
114
  unless mod.merged
103
115
  say_status 'unmerged', mod.to_s, :red unless options[:only_changed]
104
116
  next
@@ -110,26 +122,19 @@ EOF
110
122
  gsub('@PATH@', mod.work_dir)
111
123
  end
112
124
 
113
- output = repo.git.native(command, {}, *actual_args)
114
- if output.empty?
115
- say_status 'unchanged', mod.to_s, :green unless options[:only_changed]
116
- else
117
- say_status 'changed', mod.to_s, :yellow
118
- end
119
- puts output unless options[:quiet] || output.empty?
125
+ say_status command, mod.to_s
126
+ output = environment.git.git(command, *actual_args)
120
127
  end
121
128
  end
122
129
 
123
130
  desc "diff [OPTIONS] [GIT OPTIONS]",
124
131
  "Show differences between work tree and upstream module(s)"
125
- method_option :only_changed, :default => false, :type => :boolean
126
132
  def diff(*args)
127
133
  invoke :git, %w'diff' + args + %w'@MERGED@ -- @PATH@'
128
134
  end
129
135
 
130
136
  desc "log [OPTIONS] [GIT OPTIONS]",
131
137
  "Show git log of commits added to upstream module(s)"
132
- method_option :only_changed, :default => false, :type => :boolean
133
138
  def log(*args)
134
139
  invoke :git, %w'log' + args + %w'@MERGED@..HEAD -- @PATH@'
135
140
  end
@@ -175,14 +180,6 @@ EOF
175
180
  options[:modules].split(',').map(&:strip)
176
181
  end
177
182
 
178
- def conf
179
- Vendorificator::Config
180
- end
181
-
182
- def repo
183
- Vendorificator::Config.repo
184
- end
185
-
186
183
  def fail!(message, exception_message='I give up.')
187
184
  say_status('FATAL', message, :red)
188
185
  raise Thor::Error, 'I give up.'
@@ -196,30 +193,8 @@ EOF
196
193
  shell.padding -= 1
197
194
  end
198
195
 
199
- # Find proper Vendorfile
200
- def find_vendorfile
201
- given = options.file || ENV['VENDORFILE']
202
- return Pathname.new(given).expand_path if given && !given.empty?
203
-
204
- Pathname.pwd.ascend do |dir|
205
- vf = dir.join('Vendorfile')
206
- return vf if vf.exist?
207
-
208
- vf = dir.join('config/vendor.rb')
209
- return vf if vf.exist?
210
-
211
- # avoid stepping above the tmp directory when testing
212
- if ENV['VENDORIFICATOR_SPEC_RUN'] &&
213
- dir.join('vendorificator.gemspec').exist?
214
- raise RuntimeError, "Vendorfile not found"
215
- end
216
- end
217
-
218
- raise RuntimeError, "Vendorfile not found"
219
- end
220
-
221
- def ensure_clean_repo!
222
- unless repo.clean?
196
+ def ensure_clean!
197
+ unless environment.clean?
223
198
  fail!('Repository is not clean.')
224
199
  end
225
200
  end
@@ -2,21 +2,22 @@ require 'pathname'
2
2
 
3
3
  require 'mixlib/config'
4
4
 
5
- require 'vendorificator/repo'
6
-
7
5
  module Vendorificator
8
6
  class Config
9
7
  extend Mixlib::Config
10
8
 
9
+ attr_accessor :environment
10
+
11
11
  configure do |c|
12
12
  c[:basedir] = 'vendor'
13
13
  c[:branch_prefix] = 'vendor'
14
- c[:modules] = []
15
14
  c[:remotes] = %w(origin)
16
15
  end
17
16
 
18
17
  def self.from_file(filename)
19
18
  pathname = Pathname.new(filename).cleanpath.expand_path
19
+
20
+ self[:vendorfile_path] = pathname
20
21
  self[:root_dir] =
21
22
  if ( pathname.basename.to_s == 'vendor.rb' &&
22
23
  pathname.dirname.basename.to_s == 'config' )
@@ -25,46 +26,8 @@ module Vendorificator
25
26
  else
26
27
  pathname.dirname
27
28
  end
28
- self[:vendorfile_path] = pathname
29
- super(pathname.to_s)
30
- end
31
29
 
32
- def self.repo
33
- @repo ||= begin
34
- git_root_path = self[:repo_dir] || _find_git_root
35
- raise "Can't find Git repository" unless git_root_path
36
- Vendorificator::Repo.new( git_root_path.to_s )
37
- end
38
- end
39
-
40
- def self.each_module(*modules)
41
- module_paths = modules.map { |m| File.expand_path(m) }
42
-
43
- # We don't use self[:modules].each here, because mod.run! is
44
- # explicitly allowed to append to Config[:modules], and #each
45
- # fails to catch up on some Ruby implementations.
46
- i = 0
47
- while true
48
- break if i >= Vendorificator::Config[:modules].length
49
- mod = Vendorificator::Config[:modules][i]
50
- yield mod if
51
- modules.empty? ||
52
- modules.include?(mod.name) ||
53
- module_paths.include?(mod.work_dir)
54
- i += 1
55
-
56
- # Add dependencies
57
- work_dirs = Vendorificator::Config[:modules].map(&:work_dir)
58
- Vendorificator::Config[:modules] +=
59
- mod.dependencies.reject { |dep| work_dirs.include?(dep.work_dir) }
60
- end
61
- end
62
-
63
- def self._find_git_root
64
- self[:root_dir].ascend do |dir|
65
- return dir if dir.join('.git').exist?
66
- end
30
+ super(pathname.to_s)
67
31
  end
68
- private_class_method :_find_git_root
69
32
  end
70
33
  end
@@ -0,0 +1,111 @@
1
+ require 'pathname'
2
+
3
+ require 'minigit'
4
+
5
+ require 'vendorificator/config'
6
+
7
+ module Vendorificator
8
+ class Environment
9
+ attr_reader :config
10
+ attr_accessor :shell
11
+
12
+ def initialize(vendorfile=nil)
13
+ @config = Vendorificator::Config
14
+ config.environment = self
15
+ config.from_file(self.class.find_vendorfile(vendorfile))
16
+ Vendorificator::Vendor.compute_dependencies!
17
+ end
18
+
19
+ def say_status(*args)
20
+ shell.say_status(*args) if shell
21
+ end
22
+
23
+ # Main MiniGit instance
24
+ def git
25
+ @git ||= MiniGit::new(config[:vendorfile_path])
26
+ end
27
+
28
+ # Git helpers
29
+ def remotes
30
+ @remotes ||= git.capturing.remote.lines.map(&:strip)
31
+ end
32
+
33
+ def current_branch
34
+ git.capturing.rev_parse({:abbrev_ref => true}, 'HEAD').strip
35
+ end
36
+
37
+ def fast_forwardable?(to, from)
38
+ git.capturing.merge_base(to, from).strip == from
39
+ end
40
+
41
+ def clean?
42
+ # copy code from http://stackoverflow.com/a/3879077/16390
43
+ git.update_index :q => true, :ignore_submodules => true, :refresh => true
44
+ git.diff_files '--quiet', '--ignore-submodules', '--'
45
+ git.diff_index '--cached', '--quiet', 'HEAD', '--ignore-submodules', '--'
46
+ true
47
+ rescue MiniGit::GitError
48
+ false
49
+ end
50
+
51
+ def pull(remote, options={})
52
+ raise RuntimeError, "Unknown remote #{remote}" unless remotes.include?(remote)
53
+
54
+ git.fetch(remote)
55
+ git.fetch({:tags => true}, remote)
56
+
57
+ ref_rx = /^refs\/remotes\/#{Regexp.quote(remote)}\//
58
+ remote_branches = Hash[ git.capturing.show_ref.
59
+ lines.
60
+ map(&:split).
61
+ map { |sha, name| name =~ ref_rx ? [$', sha] : nil }.
62
+ compact ]
63
+
64
+ Vendorificator::Vendor.each do |mod|
65
+ ours = mod.head
66
+ theirs = remote_branches[mod.branch_name]
67
+ if theirs
68
+ if not ours
69
+ say_status 'new', mod.branch_name, :yellow
70
+ git.branch({:track=>true}, mod.branch_name, remote_head.name) unless options[:dry_run]
71
+ elsif ours == theirs
72
+ say_status 'unchanged', mod.branch_name
73
+ elsif fast_forwardable?(theirs, ours)
74
+ say_status 'updated', mod.name, :yellow
75
+ mod.in_branch { git.merge({:ff_only => true}, theirs) } unless options[:dry_run]
76
+ elsif fast_forwardable?(ours, theirs)
77
+ say_status 'older', mod.branch_name
78
+ else
79
+ say_status 'complicated', mod.branch_name, :red
80
+ end
81
+ else
82
+ say_status 'unknown', mod.branch_name
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ def self.find_vendorfile(given=nil)
89
+ given = [ given, ENV['VENDORFILE'] ].find do |candidate|
90
+ candidate && !(candidate.respond_to?(:empty?) && candidate.empty?)
91
+ end
92
+ return given if given
93
+
94
+ Pathname.pwd.ascend do |dir|
95
+ vf = dir.join('Vendorfile')
96
+ return vf if vf.exist?
97
+
98
+ vf = dir.join('config/vendor.rb')
99
+ return vf if vf.exist?
100
+
101
+ # avoid stepping above the tmp directory when testing
102
+ if ENV['VENDORIFICATOR_SPEC_RUN'] &&
103
+ dir.join('vendorificator.gemspec').exist?
104
+ raise ArgumentError, "Vendorfile not found"
105
+ end
106
+ end
107
+
108
+ raise ArgumentError, "Vendorfile not found"
109
+ end
110
+ end
111
+ end
@@ -1,36 +1,72 @@
1
1
  module Vendorificator::Hooks
2
2
  module ChefCookbookDependencies
3
+ class FakeMetadata
4
+ attr_reader :dependencies
5
+ def initialize ; @dependencies = [] ; end
6
+ def from_file(filename) ; self.instance_eval(IO.read(filename), filename, 1) ; end
7
+ def depends(*args) ; @dependencies << args ; end
8
+ def method_missing(method, *args) ; end
9
+ end
10
+
3
11
  def initialize(*args)
4
- require 'chef/cookbook/metadata'
12
+ begin
13
+ end
5
14
  super
6
15
  end
7
16
 
8
- # Add required Chef cookbooks to vendor modules
9
- def dependencies
10
- ignored =
11
- args.key?(:ignore_dependencies) ?
12
- args[:ignore_dependencies] :
13
- Vendorificator::Config[:chef_cookbook_ignore_dependencies]
14
- metadata = File.join(self.work_dir, 'metadata.rb')
15
-
16
- unless File.exist?(metadata)
17
- shell.say_status 'WARNING', "Metadata of #{name} does not exist at #{metadata}, could not gather dependencies", :red
18
- return super
19
- end
17
+ def compute_dependencies!
18
+ super
19
+
20
+ # Dependencies
21
+ ign = self.args.key?(:ignore_dependencies) ?
22
+ args[:ignore_dependencies] :
23
+ environment.config[:chef_cookbook_ignore_dependencies]
24
+
25
+ if !ign || ign.respond_to?(:include?)
26
+ metadata = File.join(self.work_dir, 'metadata.rb')
27
+
28
+ unless File.exist?(metadata)
29
+ shell.say_status 'WARNING', "Metadata of #{name} does not exist at #{metadata}, could not gather dependencies", :red
30
+ return super
31
+ end
20
32
 
21
- cbmd = Chef::Cookbook::Metadata.new
22
- cbmd.from_file(metadata)
33
+ cbmd = Vendorificator::Hooks::ChefCookbookDependencies.metadata_class.new
34
+ cbmd.from_file(metadata)
23
35
 
24
- if ignored && !ignored.respond_to?(:include?)
25
- # ignored is a truthy value that's not a set-like thing, so we
26
- # ignore all dependencies altogether.
27
- super
28
- else
36
+ basedir = Pathname.new(work_dir).dirname
37
+
38
+ # All of cookbook's dependencies
29
39
  deps = cbmd.dependencies.map(&:first)
30
- deps.reject! { |n| ignored.include?(n) } if ignored.respond_to?(:include?)
31
- deps.map! { |n| Vendorificator::Vendor::ChefCookbook.new(n) }
32
- super + deps
40
+
41
+ # Reject ignored dependencies, if there's a list
42
+ deps.reject! { |dep| ign.include?(dep) } if ign
43
+
44
+ # Reject dependencies that already have a module
45
+ deps.reject! do |dep|
46
+ dir = basedir.join(dep).to_s
47
+ Vendorificator::Vendor.instances.any? do |vi|
48
+ vi.work_dir == dir
49
+ end
50
+ end
51
+
52
+ # Create module for the dependencies
53
+ deps.each do |dep|
54
+ Vendorificator::Vendor::ChefCookbook.new(environment, dep)
55
+ end
33
56
  end
34
57
  end
58
+
59
+ private
60
+
61
+ def self.metadata_class
62
+ @metadata_class ||=
63
+ begin
64
+ require 'chef/cookbook/metadata' unless defined?(Chef::Cookbook::Metadata)
65
+ Chef::Cookbook::Metadata
66
+ rescue LoadError
67
+ # FIXME: warn
68
+ FakeMetadata
69
+ end
70
+ end
35
71
  end
36
72
  end
@@ -11,7 +11,7 @@ class Vendorificator::Vendor::Archive < Vendorificator::Vendor
11
11
  arg_reader :url, :strip_root, :type, :checksum, :filename, :basename, :extname, :unpack
12
12
  attr_reader :conjured_checksum
13
13
 
14
- def initialize(name, args={}, &block)
14
+ def initialize(environment, name, args={}, &block)
15
15
  no_url_given = !args[:url]
16
16
 
17
17
  args[:url] ||= name
@@ -48,7 +48,7 @@ class Vendorificator::Vendor::Archive < Vendorificator::Vendor
48
48
 
49
49
  name = args[:basename] if no_url_given
50
50
 
51
- super(name, args, &block)
51
+ super(environment, name, args, &block)
52
52
  end
53
53
 
54
54
  def conjure!
@@ -13,11 +13,11 @@ class Vendorificator::Vendor::ChefCookbook < Vendorificator::Vendor::Archive
13
13
 
14
14
  API_PREFIX = 'http://cookbooks.opscode.com/api/v1/cookbooks/'
15
15
 
16
- def initialize(name, args={}, &block)
16
+ def initialize(environment, name, args={}, &block)
17
17
  args[:url] ||= true # to avoid having name treated as url
18
18
  args[:filename] ||= "#{name}.tgz"
19
19
 
20
- super(name, args, &block)
20
+ super(environment, name, args, &block)
21
21
  end
22
22
 
23
23
  def api_data(v=nil)
@@ -0,0 +1,44 @@
1
+ require 'digest'
2
+ require 'open-uri'
3
+ require 'tempfile'
4
+ require 'uri'
5
+
6
+ require 'vendorificator/vendor'
7
+
8
+ class Vendorificator::Vendor::Download < Vendorificator::Vendor
9
+ arg_reader :url
10
+ attr_reader :conjured_checksum
11
+
12
+ def initialize(environment, name, args={}, &block)
13
+ no_url_given = !args[:url]
14
+
15
+ args[:url] ||= name
16
+ name = URI::parse(args[:url]).path.split('/').last if no_url_given
17
+
18
+ super(environment, name, args, &block)
19
+ end
20
+
21
+ def path
22
+ args[:path] || category
23
+ end
24
+
25
+ def conjure!
26
+ shell.say_status :download, url
27
+ File.open name, 'w' do |outf|
28
+ outf.write( open(url).read )
29
+ end
30
+ @conjured_checksum = Digest::SHA256.file(name).hexdigest
31
+ end
32
+
33
+ def upstream_version
34
+ conjured_checksum || Digest::SHA256.hexdigest( open(url).read )
35
+ end
36
+
37
+ def conjure_commit_message
38
+ rv = "Conjured #{name} from #{url}\nChecksum: #{conjured_checksum}"
39
+ rv << "Version: #{args[:version]}" if args[:version]
40
+ rv
41
+ end
42
+
43
+ install!
44
+ end
@@ -1,34 +1,32 @@
1
1
  require 'fileutils'
2
-
3
- require 'grit'
4
-
5
2
  require 'vendorificator/vendor'
6
3
 
7
4
  class Vendorificator::Vendor::Git < Vendorificator::Vendor
8
5
  arg_reader :repository, :revision, :branch
9
- attr_reader :module_repo, :conjured_revision
6
+ attr_reader :git, :conjured_revision
10
7
 
11
- def initialize(name, args={}, &block)
8
+ def initialize(environment, name, args={}, &block)
12
9
  unless args.include?(:repository)
13
10
  args[:repository] = name
14
11
  name = name.split('/').last.sub(/\.git$/, '')
15
12
  end
16
- super(name, args, &block)
13
+ super(environment, name, args, &block)
17
14
  end
18
15
 
19
16
  def conjure!
20
17
  shell.say_status :clone, repository
21
- Grit::Git.new('.').clone({}, repository, '.')
22
- @module_repo = Grit::Repo.new('.')
18
+ MiniGit.git :clone, repository, '.'
19
+ @git = MiniGit.new('.')
23
20
 
24
21
  if revision
25
- module_repo.git.checkout({:b => 'vendorified'}, revision)
22
+ git.checkout({:b => 'vendorified'}, revision)
26
23
  elsif branch
27
- module_repo.git.checkout({:b => 'vendorified'}, "origin/#{branch}")
24
+ git.checkout({:b => 'vendorified'}, "origin/#{branch}")
28
25
  end
29
26
 
30
27
  super
31
- @conjured_revision = module_repo.head.commit.id
28
+
29
+ @conjured_revision = git.capturing.rev_parse('HEAD').strip
32
30
  FileUtils::rm_rf '.git'
33
31
  end
34
32