vendorificator 0.1.1 → 0.2.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 (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