vanagon 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/git/basic_submodules.rb +53 -0
  3. data/lib/vanagon/component.rb +19 -13
  4. data/lib/vanagon/component/dsl.rb +6 -5
  5. data/lib/vanagon/component/source.rb +80 -53
  6. data/lib/vanagon/component/source/git.rb +129 -20
  7. data/lib/vanagon/component/source/http.rb +41 -73
  8. data/lib/vanagon/component/source/local.rb +105 -62
  9. data/lib/vanagon/platform/deb.rb +8 -0
  10. data/lib/vanagon/platform/rpm.rb +4 -0
  11. data/lib/vanagon/project/dsl.rb +3 -1
  12. data/lib/vanagon/utilities.rb +11 -54
  13. data/spec/fixtures/files/fake_file_ext.7z +0 -0
  14. data/spec/fixtures/files/fake_file_ext.bz +0 -0
  15. data/spec/fixtures/files/fake_file_ext.bz2 +0 -0
  16. data/spec/fixtures/files/fake_file_ext.cpio +0 -0
  17. data/spec/fixtures/files/fake_file_ext.gz +0 -0
  18. data/spec/fixtures/files/fake_file_ext.rar +0 -0
  19. data/spec/fixtures/files/fake_file_ext.tar +0 -0
  20. data/spec/fixtures/files/fake_file_ext.tar.bz2 +0 -0
  21. data/spec/fixtures/files/fake_file_ext.tar.xz +0 -0
  22. data/spec/fixtures/files/fake_file_ext.tbz +0 -0
  23. data/spec/fixtures/files/fake_file_ext.tbz2 +0 -0
  24. data/spec/fixtures/files/fake_file_ext.txz +0 -0
  25. data/spec/fixtures/files/fake_file_ext.xz +0 -0
  26. data/spec/fixtures/files/fake_file_ext.z +0 -0
  27. data/spec/lib/vanagon/component/source/git_spec.rb +25 -17
  28. data/spec/lib/vanagon/component/source/http_spec.rb +2 -24
  29. data/spec/lib/vanagon/component/source/{localsource_spec.rb → local_spec.rb} +8 -8
  30. data/spec/lib/vanagon/component/source_spec.rb +150 -67
  31. data/spec/lib/vanagon/platform_spec.rb +40 -21
  32. data/spec/lib/vanagon/project/dsl_spec.rb +28 -3
  33. data/spec/lib/vanagon/utilities_spec.rb +0 -44
  34. metadata +28 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02183e0db1fb5c4fa7a207d9a24c9c3f2e9c95c6
4
- data.tar.gz: 948b629fcfbf13083f409eacc6681fe7004eee29
3
+ metadata.gz: 9b41f01dda75f8d40767ea3c7e290cf11462ff94
4
+ data.tar.gz: 7c9b7ce98ff34e60371dbf087366cb0fd488c53d
5
5
  SHA512:
6
- metadata.gz: e283e4e4ae27a37619e59aa43c50037e2daae3263143e740885c247e10cb8fae7452f57132edb21b6fc718cc0172ceb02c718807767079618e343205afbdb208
7
- data.tar.gz: dd1da7f1081686fd628e518c258189239335c78f86cf8daf7ac50ad4e944135e6661f4faa61296435cc5efcc39b1625e3d8ce4223b2ea4811569779fadcf499a
6
+ metadata.gz: 5dda1a3fde0dfb600e38da84061bfd3f006b4f8a199e04900d067a4a96408affb102e916622d88b8fa0e04f6773f686bf5e2ef13aef108c5560d2c42e009df54
7
+ data.tar.gz: 4263fedb95452fefc118f7a199d70756455cb02c0fadd1c36b9ad630cdfd8d67d647c7a358c683295ca5cc0d2c53f2eca1a5a1ee4efde71b9d91f02a864118f5
@@ -0,0 +1,53 @@
1
+ require 'git'
2
+
3
+ module BasicSubmodulePrimitives
4
+ # Extend Git::Lib to support shotgunning submodules. This command
5
+ # is not smart, and it has very poor support for submodule options.
6
+ # For example, you can't pass any arguments to the handful of submodule
7
+ # options that accept them (like --depth). We may extend it later,
8
+ # but for rev. 0001, simply initializing them will suffice
9
+ def update_submodules(**options)
10
+ arr_opts = ['update']
11
+ options.each_pair do |k, v|
12
+ arr_opts << "--#{k}" if v
13
+ end
14
+ Dir.chdir(@git_work_dir) do
15
+ command('submodule', arr_opts)
16
+ end
17
+ end
18
+ end
19
+
20
+ module BasicSubmodules
21
+ # @example Initialize all git submodules
22
+ # >> repo = Git.clone("git@github.com:puppetlabs/facter.git", "facter", path: Dir.mktmpdir)
23
+ # => <Git::Base>
24
+ # >> repo.checkout "3.1.3
25
+ # => <String>
26
+ # >> repo.update_submodules(init: true)
27
+ # => <String>
28
+ # @param [Hash] options any options to pass to `git submodule update`
29
+ # @option options [Boolean] :init whether to initialize submodules when updating them
30
+ # @option options [Boolean] :use the submodule's remote-tracking branch instead of superproject's SHA1 sum
31
+ # @option options [Boolean] :no-fetch don't fetch new objects from the remote site.
32
+ # @option options [Boolean] :force remove submodule's working tree even if modified
33
+ # @option options [Boolean] :checkout checkout submodules in detached HEAD state
34
+ # @option options [Boolean] :merge merge recorded commit for submodule into the current branch of the submodule
35
+ # @option options [Boolean] :rebase rebase current branch of submodule onto the commit recorded in the superproject
36
+ # @option options [Boolean] :recursive recurse into nested submodules
37
+ # @return options [String] any output produced by `git` when submodules are initialized
38
+ def update_submodules(**options)
39
+ self.lib.update_submodules(options)
40
+ end
41
+ end
42
+
43
+ module Git
44
+ class Lib
45
+ include BasicSubmodulePrimitives
46
+ end
47
+ end
48
+
49
+ module Git
50
+ class Base
51
+ include BasicSubmodules
52
+ end
53
+ end
@@ -105,17 +105,20 @@ class Vanagon
105
105
  # makefile template
106
106
  #
107
107
  # @param workdir [String] working directory to put the source into
108
- def get_source(workdir)
109
- if @url
110
- @source = Vanagon::Component::Source.source(@url, @options, workdir)
111
- @source.fetch
112
- @source.verify
113
- @extract_with = @source.respond_to?(:extract) ? @source.extract(@platform.tar) : ':'
114
- @cleanup_source = @source.cleanup if @source.respond_to?(:cleanup)
115
- @dirname = @source.dirname
108
+ def get_source(workdir) # rubocop:disable Metrics/AbcSize
109
+ opts = options.merge({ workdir: workdir })
110
+ if url
111
+ @source = Vanagon::Component::Source.source(url, opts)
112
+ source.fetch
113
+ source.verify
114
+ @extract_with = source.respond_to?(:extract) ? source.extract(platform.tar) : ':'
115
+ @cleanup_source = source.cleanup if source.respond_to?(:cleanup)
116
+ @dirname = source.dirname
116
117
 
117
118
  # Git based sources probably won't set the version, so we load it if it hasn't been already set
118
- @version ||= @source.version
119
+ if source.respond_to?(:version)
120
+ @version ||= source.version
121
+ end
119
122
  else
120
123
  warn "No source given for component '#{@name}'"
121
124
 
@@ -140,10 +143,13 @@ class Vanagon
140
143
  #
141
144
  # @param workdir [String] working directory to put the source into
142
145
  def get_sources(workdir)
143
- @sources.each do |source|
144
- cur_source = Vanagon::Component::Source.source(source.url, { :ref => source.ref, :sum => source.sum }, workdir)
145
- cur_source.fetch
146
- cur_source.verify
146
+ sources.each do |source|
147
+ src = Vanagon::Component::Source.source source.url,
148
+ workdir: workdir,
149
+ ref: source.ref,
150
+ sum: source.sum
151
+ src.fetch
152
+ src.verify
147
153
  end
148
154
  end
149
155
 
@@ -310,11 +310,12 @@ class Vanagon
310
310
 
311
311
  # This will add a source to the project and put it in the workdir alongside the other sources
312
312
  #
313
- # @param url [String] url of the source
314
- # @param ref [String] Used for git sources, must be a git ref of some sort
315
- # @param sum [String] sum used to validate http and file sources
316
- def add_source(url, ref: nil, sum: nil)
317
- @component.sources << OpenStruct.new(:url => url, :ref => ref, :sum => sum)
313
+ # @param uri [String] uri of the source
314
+ # @param [Hash] options optional keyword arguments used to instatiate a new source
315
+ # @option opts [String] :sum
316
+ # @option opts [String] :ref
317
+ def add_source(uri, **options)
318
+ @component.sources << OpenStruct.new(options.merge({ url: uri }))
318
319
  end
319
320
 
320
321
  # Adds a directory to the list of directories provided by the project, to be included in any packages of the project
@@ -1,3 +1,4 @@
1
+ require 'fustigit'
1
2
  require 'vanagon/component/source/http'
2
3
  require 'vanagon/component/source/git'
3
4
  require 'vanagon/component/source/local'
@@ -5,74 +6,100 @@ require 'vanagon/component/source/local'
5
6
  class Vanagon
6
7
  class Component
7
8
  class Source
8
- SUPPORTED_PROTOCOLS = ['file', 'http', 'git'].freeze
9
- @@rewrite_rule = {}
9
+ SUPPORTED_PROTOCOLS = %w(file http https git).freeze
10
+ @rewrite_rules = {}
10
11
 
11
- def self.register_rewrite_rule(protocol, rule)
12
- if rule.is_a?(String) or rule.is_a?(Proc)
13
- if SUPPORTED_PROTOCOLS.include?(protocol)
14
- @@rewrite_rule[protocol] = rule
12
+ class << self
13
+ attr_reader :rewrite_rules
14
+
15
+ def register_rewrite_rule(protocol, rule)
16
+ if rule.is_a?(String) || rule.is_a?(Proc)
17
+ if SUPPORTED_PROTOCOLS.include?(protocol)
18
+ @rewrite_rules[protocol] = rule
19
+ else
20
+ raise Vanagon::Error, "#{protocol} is not a supported protocol for rewriting"
21
+ end
15
22
  else
16
- raise Vanagon::Error, "#{protocol} is not a supported protocol for rewriting"
23
+ raise Vanagon::Error, "String or Proc is required as a rewrite_rule."
17
24
  end
18
- else
19
- raise Vanagon::Error, "String or Proc is required as a rewrite_rule."
20
25
  end
21
- end
22
26
 
23
- def self.rewrite(url, protocol)
24
- rule = @@rewrite_rule[protocol]
27
+ def rewrite(url, protocol)
28
+ # Vanagon did not originally distinguish between http and https
29
+ # when looking up rewrite rules; this is no longer true, but it
30
+ # means that we should try to preserve old, dumb behavior until
31
+ # the rewrite engine is removed.
32
+ return rewrite(url, "http") if protocol == "https"
25
33
 
26
- if rule
27
- if rule.is_a?(Proc)
28
- return proc_rewrite(rule, url)
29
- elsif rule.is_a?(String)
30
- return string_rewrite(rule, url)
34
+ rule = @rewrite_rules[protocol]
35
+ if rule
36
+ if rule.is_a?(Proc)
37
+ return proc_rewrite(rule, url)
38
+ elsif rule.is_a?(String)
39
+ return string_rewrite(rule, url)
40
+ end
31
41
  end
42
+
43
+ return url
32
44
  end
33
45
 
34
- return url
35
- end
46
+ def proc_rewrite(rule, url)
47
+ if rule.arity == 1
48
+ rule.call(url)
49
+ else
50
+ raise Vanagon::Error, "Unable to use provided rewrite rule. Expected Proc with one argument, Proc has #{rule.arity} arguments"
51
+ end
52
+ end
36
53
 
37
- def self.proc_rewrite(rule, url)
38
- if rule.arity == 1
39
- rule.call(url)
40
- else
41
- raise Vanagon::Error, "Unable to use provided rewrite rule. Expected Proc with one argument, Proc has #{rule.arity} arguments"
54
+ def string_rewrite(rule, original_url)
55
+ url = original_url.to_s
56
+ target_match = url.match(/.*\/([^\/]*)$/)
57
+ if target_match
58
+ target = target_match[1]
59
+ return File.join(rule, target)
60
+ else
61
+ raise Vanagon::Error, "Unable to apply url rewrite to '#{url}', expected to find at least one '/' in the url."
62
+ end
42
63
  end
43
- end
44
64
 
45
- def self.string_rewrite(rule, url)
46
- target_match = url.match(/.*\/([^\/]*)$/)
47
- if target_match
48
- target = target_match[1]
49
- return File.join(rule, target)
50
- else
51
- raise Vanagon::Error, "Unable to apply url rewrite to '#{url}', expected to find at least one '/' in the url."
65
+ def parse_and_rewrite(uri)
66
+ url = URI.parse(uri)
67
+ return url unless url.scheme
68
+ rewrite(url.to_s, url.scheme)
52
69
  end
53
- end
54
70
 
55
- # Basic factory to hand back the correct {Vanagon::Component::Source} subtype to the component
56
- #
57
- # @param url [String] URL to the source (includes git@... style links)
58
- # @param options [Hash] hash of the options needed for the subtype
59
- # @param workdir [String] working directory to fetch the source into
60
- # @return [Vanagon::Component::Source] the correct subtype for the given source
61
- def self.source(url, options, workdir)
62
- url_match = url.match(/^(.*)(@|:\/\/)(.*)$/)
63
- uri_scheme = url_match[1] if url_match
64
- local_source = case uri_scheme
65
- when /^http/
66
- Vanagon::Component::Source::Http.new(self.rewrite(url, 'http'), options[:sum], workdir)
67
- when /^file/
68
- Vanagon::Component::Source::Local.new(self.rewrite(url, 'file'), workdir)
69
- when /^git/
70
- Vanagon::Component::Source::Git.new(self.rewrite(url, 'git'), options[:ref], workdir)
71
- else
72
- fail "Don't know how to handle source of type '#{uri_scheme}' from url: '#{url}'"
73
- end
71
+ # Basic factory to hand back the correct {Vanagon::Component::Source} subtype to the component
72
+ #
73
+ # @param url [String] URI of the source file (includes git@... style links)
74
+ # @param options [Hash] hash of the options needed for the subtype
75
+ # @param workdir [String] working directory to fetch the source into
76
+ # @return [Vanagon::Component::Source] the correct subtype for the given source
77
+ def source(uri, **options) # rubocop:disable Metrics/AbcSize
78
+ # First we try git
79
+ if Vanagon::Component::Source::Git.valid_remote?(parse_and_rewrite(uri))
80
+ return Vanagon::Component::Source::Git.new parse_and_rewrite(uri),
81
+ sum: options[:sum],
82
+ ref: options[:ref],
83
+ workdir: options[:workdir]
84
+ end
85
+
86
+ # Then we try HTTP
87
+ if Vanagon::Component::Source::Http.valid_url?(parse_and_rewrite(uri))
88
+ return Vanagon::Component::Source::Http.new parse_and_rewrite(uri),
89
+ sum: options[:sum],
90
+ workdir: options[:workdir]
91
+ end
92
+
93
+ # Then we try local
94
+ if Vanagon::Component::Source::Local.valid_file?(uri)
95
+ return Vanagon::Component::Source::Local.new uri,
96
+ workdir: options[:workdir]
97
+ end
74
98
 
75
- return local_source
99
+ # Failing all of that, we give up
100
+ raise Vanagon::Error,
101
+ "Unknown file type: '#{uri}'; cannot continue"
102
+ end
76
103
  end
77
104
  end
78
105
  end
@@ -1,39 +1,69 @@
1
1
  require 'vanagon/utilities'
2
+ require 'vanagon/errors'
3
+ # This stupid library requires a capital 'E' in its name
4
+ # but it provides a wealth of useful constants
5
+ require 'English'
6
+ require 'fustigit'
7
+ require 'git/basic_submodules'
8
+ require 'logger'
2
9
 
3
10
  class Vanagon
4
11
  class Component
5
12
  class Source
6
13
  class Git
7
- include Vanagon::Utilities
8
- attr_accessor :url, :ref, :workdir, :version, :cleanup
14
+ attr_accessor :url, :ref, :workdir
15
+ attr_reader :version, :default_options, :repo
16
+
17
+ class << self
18
+ # Attempt to connect to whatever URL is provided and
19
+ # return True or False depending on whether or not
20
+ # `git` thinks it's a valid Git repo.
21
+ #
22
+ # @return [Boolean] whether #url is a valid Git repo or not
23
+ def valid_remote?(url)
24
+ !!::Git.ls_remote(url)
25
+ rescue ::Git::GitExecuteError
26
+ false
27
+ end
28
+ end
29
+
30
+ # Default options used when cloning; this may expand
31
+ # or change over time.
32
+ def default_options
33
+ @default_options ||= { ref: "refs/heads/master" }
34
+ end
35
+ private :default_options
9
36
 
10
37
  # Constructor for the Git source type
11
38
  #
12
39
  # @param url [String] url of git repo to use as source
13
40
  # @param ref [String] ref to checkout from git repo
14
41
  # @param workdir [String] working directory to clone into
15
- def initialize(url, ref, workdir)
16
- unless ref
17
- fail "ref parameter is required for the git source"
18
- end
19
- @url = url
20
- @ref = ref
42
+ def initialize(url, workdir:, **options)
43
+ opts = default_options.merge(options)
44
+
45
+ # Ensure that #url returns a URI object
46
+ @url = URI.parse(url.to_s)
47
+ @ref = opts[:ref]
21
48
  @workdir = workdir
49
+ @ref_name, @ref_type, = @ref.split('/', 3).reverse
50
+
51
+ # We can test for Repo existence without cloning
52
+ raise Vanagon::InvalidRepo, "#{url} not a valid Git repo" unless valid_remote?
22
53
  end
23
54
 
24
55
  # Fetch the source. In this case, clone the repository into the workdir
25
56
  # and check out the ref. Also sets the version if there is a git tag as
26
57
  # a side effect.
27
58
  def fetch
28
- puts "Cloning ref '#{@ref}' from url '#{@url}'"
29
- Dir.chdir(@workdir) do
30
- git("clone #{@url}", true)
31
- Dir.chdir(dirname) do
32
- git("checkout #{@ref}", true)
33
- git("submodule update --init --recursive", true)
34
- @version = git_version
35
- end
36
- end
59
+ clone!
60
+ checkout!
61
+ version
62
+ update_submodules
63
+ end
64
+
65
+ def ref
66
+ @ref_name || @ref
37
67
  end
38
68
 
39
69
  # Return the correct incantation to cleanup the source directory for a given source
@@ -43,18 +73,97 @@ class Vanagon
43
73
  "rm -rf #{dirname}"
44
74
  end
45
75
 
46
- # There is no md5 to manually verify here, so it is a noop.
76
+ # There is no md5 to manually verify here, so this is a noop.
47
77
  def verify
48
- # nothing to do here, so just return
78
+ # nothing to do here, so just tell users that and return
79
+ puts "Nothing to verify for '#{dirname}' (using Git reference '#{ref}')"
49
80
  end
50
81
 
51
82
  # The dirname to reference when building from the repo
52
83
  #
53
84
  # @return [String] the directory where the repo was cloned
54
85
  def dirname
55
- File.basename(@url).sub(/\.git/, '')
86
+ File.basename(url.path, ".git")
87
+ end
88
+
89
+ # Use `git describe` to lazy-load a version for this component
90
+ def version
91
+ @version ||= describe
92
+ end
93
+
94
+ # Perform a git clone of @url as a lazy-loaded
95
+ # accessor for @clone
96
+ def clone
97
+ @clone ||= ::Git.clone(url, dirname, path: workdir)
98
+ end
99
+
100
+ # Attempt to connect to whatever URL is provided and
101
+ # return True or False depending on whether or not
102
+ # `git` thinks it's a valid Git repo.
103
+ #
104
+ # @return [Boolean] whether #url is a valid Git repo or not
105
+ def valid_remote?
106
+ self.class.valid_remote? url
107
+ end
108
+ private :valid_remote?
109
+
110
+ # Provide a list of remote refs (branches and tags)
111
+ def remote_refs
112
+ (remote['tags'].keys + remote['branches'].keys).uniq
113
+ end
114
+ private :remote_refs
115
+
116
+ # Provide a list of local refs (branches and tags)
117
+ def refs
118
+ (clone.tags.map(&:name) + clone.branches.map(&:name)).uniq
119
+ end
120
+ private :refs
121
+
122
+ # Clone a remote repo, make noise about it, and fail entirely
123
+ # if we're unable to retrieve the remote repo
124
+ def clone!
125
+ puts "Cloning Git repo '#{url}'"
126
+ puts "Successfully cloned '#{dirname}'" if clone
127
+ rescue Git::GitExecuteError
128
+ raise Vanagon::InvalidRepo, "Unable to clone from '#{url}'"
56
129
  end
130
+ private :clone!
131
+
132
+ # Checkout desired ref/sha, make noise about it, and fail
133
+ # entirely if we're unable to checkout that given ref/sha
134
+ def checkout!
135
+ puts "Checking out '#{ref}'' from Git repo '#{dirname}'"
136
+ clone.checkout(ref)
137
+ rescue ::Git::GitExecuteError
138
+ raise Vanagon::CheckoutFailed, "unable to checkout #{ref} from '#{url}'"
139
+ end
140
+ private :checkout!
141
+
142
+ # Attempt to update submodules, and do not panic
143
+ # if there are no submodules to initialize
144
+ def update_submodules
145
+ puts "Attempting to update submodules for repo '#{dirname}'"
146
+ clone.update_submodules(init: true)
147
+ end
148
+ private :update_submodules
149
+
150
+ # Determines a version for the given directory based on the git describe
151
+ # for the repository
152
+ #
153
+ # @return [String] The version of the directory according to git describe
154
+ def describe
155
+ clone.describe(ref, tags: true)
156
+ rescue ::Git::GitExecuteError
157
+ warn "Directory '#{dirname}' cannot be versioned by Git. Maybe it hasn't been tagged yet?"
158
+ end
159
+ private :describe
57
160
  end
58
161
  end
59
162
  end
163
+
164
+ class GitError < Error; end
165
+ # Raised when a URI is not a valid Source Control repo
166
+ class InvalidRepo < GitError; end
167
+ # Raised when checking out a given ref from a Git Repo fails
168
+ class CheckoutFailed < GitError; end
60
169
  end