r10k 1.1.4 → 1.2.0rc1

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 (69) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +1 -0
  3. data/.nodeset.yml +7 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +2 -1
  6. data/CHANGELOG +17 -12
  7. data/Gemfile +8 -0
  8. data/README.markdown +4 -2
  9. data/Rakefile +1 -0
  10. data/doc/dynamic-environments.markdown +206 -0
  11. data/doc/puppetfile.markdown +87 -0
  12. data/lib/r10k/cli.rb +1 -1
  13. data/lib/r10k/errors.rb +30 -3
  14. data/lib/r10k/execution.rb +5 -2
  15. data/lib/r10k/git/cache.rb +26 -42
  16. data/lib/r10k/git/commit.rb +22 -0
  17. data/lib/r10k/git/errors.rb +31 -22
  18. data/lib/r10k/git/head.rb +33 -0
  19. data/lib/r10k/git/ref.rb +63 -0
  20. data/lib/r10k/git/repository.rb +65 -36
  21. data/lib/r10k/git/tag.rb +26 -0
  22. data/lib/r10k/git/working_dir.rb +93 -83
  23. data/lib/r10k/git.rb +14 -0
  24. data/lib/r10k/module/forge.rb +129 -62
  25. data/lib/r10k/module/git.rb +72 -6
  26. data/lib/r10k/module/metadata.rb +47 -0
  27. data/lib/r10k/module/svn.rb +99 -0
  28. data/lib/r10k/module.rb +1 -0
  29. data/lib/r10k/module_repository/forge.rb +64 -0
  30. data/lib/r10k/module_repository.rb +8 -0
  31. data/lib/r10k/semver.rb +1 -1
  32. data/lib/r10k/svn/working_dir.rb +76 -0
  33. data/lib/r10k/task/deployment.rb +21 -28
  34. data/lib/r10k/util/subprocess/io.rb +12 -0
  35. data/lib/r10k/util/subprocess/result.rb +36 -0
  36. data/lib/r10k/util/subprocess/runner.rb +88 -0
  37. data/lib/r10k/util/subprocess.rb +107 -0
  38. data/lib/r10k/version.rb +1 -1
  39. data/r10k.gemspec +11 -1
  40. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/and_the_expected_version_is_latest/can_fetch_all_versions_of_a_given_module.yml +42 -0
  41. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/and_the_expected_version_is_latest/can_fetch_the_latest_version_of_a_given_module.yml +42 -0
  42. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/looking_up_versions/can_fetch_all_versions_of_a_given_module.yml +42 -0
  43. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/looking_up_versions/can_fetch_the_latest_version_of_a_given_module.yml +42 -0
  44. data/spec/fixtures/vcr/cassettes/R10K_ModuleRepository_Forge/looking_up_versions.yml +42 -0
  45. data/spec/fixtures/vcr/cassettes/R10K_Module_Forge/and_the_expected_version_is_latest/sets_the_expected_version_based_on_the_latest_forge_version.yml +42 -0
  46. data/spec/rspec-system-r10k/puppetfile.rb +24 -0
  47. data/spec/rspec-system-r10k/tmpdir.rb +32 -0
  48. data/spec/shared-examples/git-ref.rb +49 -0
  49. data/spec/spec_helper.rb +23 -0
  50. data/spec/system/module/forge/install_spec.rb +51 -0
  51. data/spec/system/module/git/install_spec.rb +117 -0
  52. data/spec/system/module/svn/install_spec.rb +51 -0
  53. data/spec/system/module/svn/update_spec.rb +38 -0
  54. data/spec/system/spec_helper.rb +60 -0
  55. data/spec/system/system-helpers.rb +4 -0
  56. data/spec/system/version_spec.rb +7 -0
  57. data/spec/system-provisioning/el.rb +38 -0
  58. data/spec/unit/deployment/source_spec.rb +1 -1
  59. data/spec/unit/git/cache_spec.rb +38 -0
  60. data/spec/unit/git/commit_spec.rb +33 -0
  61. data/spec/unit/git/head_spec.rb +27 -0
  62. data/spec/unit/git/ref_spec.rb +68 -0
  63. data/spec/unit/git/tag_spec.rb +31 -0
  64. data/spec/unit/module/forge_spec.rb +157 -37
  65. data/spec/unit/module/git_spec.rb +49 -0
  66. data/spec/unit/module/metadata_spec.rb +68 -0
  67. data/spec/unit/module/svn_spec.rb +146 -0
  68. data/spec/unit/module_repository/forge_spec.rb +32 -0
  69. metadata +151 -8
@@ -0,0 +1,26 @@
1
+ require 'r10k/git/ref'
2
+ require 'r10k/git/repository'
3
+
4
+ # tag: A ref under refs/tags/ namespace that points to an object of an
5
+ # arbitrary type (typically a tag points to either a tag or a commit object).
6
+ # In contrast to a head, a tag is not updated by the commit command. A tag is
7
+ # most typically used to mark a particular point in the commit ancestry chain.
8
+ #
9
+ # @see https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html
10
+ # @api private
11
+ class R10K::Git::Tag < R10K::Git::Ref
12
+
13
+ # @!attribute [r] tag
14
+ # @return [String] The git tag
15
+ attr_reader :tag
16
+ alias :ref :tag
17
+
18
+ def initialize(tag, repository = nil)
19
+ @tag = tag
20
+ @repository = repository
21
+ end
22
+
23
+ def fetch?
24
+ ! resolvable?
25
+ end
26
+ end
@@ -1,15 +1,14 @@
1
1
  require 'forwardable'
2
2
  require 'r10k/logging'
3
+ require 'r10k/git'
3
4
  require 'r10k/git/cache'
4
5
 
5
- module R10K
6
- module Git
7
- class WorkingDir < R10K::Git::Repository
8
- # Implements sparse git repositories with shared objects
9
- #
10
- # Working directory instances use the git alternatives object store, so that
11
- # working directories only contain checked out files and all object files are
12
- # shared.
6
+ # Implements sparse git repositories with shared objects
7
+ #
8
+ # Working directory instances use the git alternatives object store, so that
9
+ # working directories only contain checked out files and all object files are
10
+ # shared.
11
+ class R10K::Git::WorkingDir < R10K::Git::Repository
13
12
 
14
13
  include R10K::Logging
15
14
 
@@ -20,124 +19,135 @@ class WorkingDir < R10K::Git::Repository
20
19
  attr_reader :cache
21
20
 
22
21
  # @!attribute [r] ref
23
- # @return [String] The git reference to use check out in the given directory
22
+ # @return [R10K::Git::Ref] The git reference to use check out in the given directory
24
23
  attr_reader :ref
25
24
 
26
- # Instantiates a new git synchro and optionally prepares for caching
25
+ # @!attribute [r] remote
26
+ # @return [String] The actual remote used as an upstream for this module.
27
+ attr_reader :remote
28
+
29
+ # Create a new shallow git working directory
27
30
  #
28
- # @param [String] ref
29
- # @param [String] remote
30
- # @param [String] basedir
31
- # @param [String] dirname
31
+ # @param ref [String, R10K::Git::Ref]
32
+ # @param remote [String]
33
+ # @param basedir [String]
34
+ # @param dirname [String]
32
35
  def initialize(ref, remote, basedir, dirname = nil)
33
- @ref = ref
36
+
34
37
  @remote = remote
35
38
  @basedir = basedir
36
39
  @dirname = dirname || ref
37
40
 
38
41
  @full_path = File.join(@basedir, @dirname)
42
+ @git_dir = File.join(@full_path, '.git')
39
43
 
40
44
  @cache = R10K::Git::Cache.generate(@remote)
45
+
46
+ if ref.is_a? String
47
+ @ref = R10K::Git::Ref.new(ref, self)
48
+ else
49
+ @ref = ref
50
+ @ref.repository = self
51
+ end
41
52
  end
42
53
 
43
54
  # Synchronize the local git repository.
44
55
  def sync
45
- # TODO stop forcing a sync every time.
46
- @cache.sync
47
-
48
- if cloned?
49
- fetch
50
- else
56
+ if not cloned?
51
57
  clone
58
+ elsif fetch?
59
+ fetch_from_cache
60
+ checkout(@ref)
61
+ elsif needs_checkout?
62
+ checkout(@ref)
63
+ end
64
+ end
65
+
66
+ def update
67
+ if fetch?
68
+ fetch_from_cache
69
+ checkout(@ref)
70
+ elsif needs_checkout?
71
+ checkout(@ref)
52
72
  end
53
- reset
54
73
  end
55
74
 
56
75
  # Determine if repo has been cloned into a specific dir
57
76
  #
58
77
  # @return [true, false] If the repo has already been cloned
59
78
  def cloned?
60
- File.directory? git_dir
79
+ File.directory? @git_dir
61
80
  end
81
+ alias :git? :cloned?
62
82
 
63
- private
83
+ # Does a directory exist where we expect a working dir to be?
84
+ # @return [true, false]
85
+ def exist?
86
+ File.directory? @full_path
87
+ end
64
88
 
65
- def set_cache_remote
66
- # XXX This is crude but it'll ensure that the right remote is used for
67
- # the cache.
68
- if remote_url('cache') == @cache.path
69
- logger.debug1 "Git repo #{@full_path} cache remote already set correctly"
89
+ # check out the given ref
90
+ #
91
+ # @param ref [R10K::Git::Ref] The git reference to check out
92
+ def checkout(ref)
93
+ if ref.resolvable?
94
+ git ["checkout", "--force", @ref.sha1], :path => @full_path
70
95
  else
71
- git "remote set-url cache #{@cache.path}", :path => @full_path
96
+ raise R10K::Git::UnresolvableRefError.new(
97
+ "Cannot check out unresolvable ref '#{@ref}'",
98
+ :git_dir => @full_path)
72
99
  end
73
100
  end
74
101
 
75
- # Perform a non-bare clone of a git repository.
76
- def clone
77
- # We do the clone against the target repo using the `--reference` flag so
78
- # that doing a normal `git pull` on a directory will work.
79
- git "clone --reference #{@cache.path} #{@remote} #{@full_path}"
80
- git "remote add cache #{@cache.path}", :path => @full_path
81
- git "checkout #{@ref}", :path => @full_path
102
+ # The currently checked out HEAD
103
+ #
104
+ # @return [R10k::Git::Head]
105
+ def current
106
+ R10K::Git::Head.new('HEAD', self)
82
107
  end
83
108
 
84
- def fetch
85
- set_cache_remote
86
- git "fetch --prune cache", :path => @full_path
109
+ def outdated?
110
+ @ref.fetch? or needs_checkout?
87
111
  end
88
112
 
89
- # Reset a git repo with a working directory to a specific ref
90
- def reset
91
- commit = cache.rev_parse(@ref)
92
- current = rev_parse('HEAD')
93
-
94
- if commit == current
95
- logger.debug1 "Git repo #{@full_path} is already at #{commit}, no need to reset"
96
- return
97
- end
113
+ private
98
114
 
99
- begin
100
- git "reset --hard #{commit}", :path => @full_path
101
- rescue R10K::ExecutionFailure => e
102
- logger.error "Unable to locate commit object #{commit} in git repo #{@full_path}"
103
- raise
104
- end
115
+ def fetch?
116
+ @ref.fetch?
105
117
  end
106
118
 
107
- # Resolve a ref to a commit hash
108
- #
109
- # @param [String] ref
110
- #
111
- # @return [String] The dereferenced hash of `ref`
112
- def rev_parse(ref)
113
- commit = git "rev-parse #{ref}^{commit}", :path => @full_path
114
- commit.chomp
115
- rescue R10K::ExecutionFailure => e
116
- logger.error "Could not resolve ref #{ref.inspect} for git repo #{@full_path}"
117
- raise
119
+ def fetch_from_cache
120
+ set_cache_remote
121
+ @cache.sync
122
+ fetch('cache')
118
123
  end
119
124
 
120
- # @param [String] name The remote to retrieve the URl for
121
- # @return [String] The git remote URL
122
- def remote_url(remote_name)
123
- output = git "remote -v", :path => @full_path
125
+ def set_cache_remote
126
+ if self.remote != @cache.remote
127
+ git ["remote", "set-url", "cache", @cache.git_dir], :path => @full_path
128
+ end
129
+ end
124
130
 
125
- remotes = {}
131
+ # Perform a non-bare clone of a git repository.
132
+ def clone
133
+ @cache.sync
126
134
 
127
- output.each_line do |line|
128
- if mdata = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/)
129
- name = mdata[1]
130
- remote = mdata[2]
131
- remotes[name] = remote
132
- end
133
- end
135
+ # We do the clone against the target repo using the `--reference` flag so
136
+ # that doing a normal `git pull` on a directory will work.
137
+ git ["clone", "--reference", @cache.git_dir, @remote, @full_path]
138
+ git ["remote", "add", "cache", @cache.git_dir], :path => @full_path
139
+ checkout(@ref)
140
+ end
134
141
 
135
- remotes[remote_name]
142
+ def needs_fetch?
143
+ ref.fetch?
136
144
  end
137
145
 
138
- def git_dir
139
- File.join(@full_path, '.git')
146
+ # Does the expected ref match the actual ref?
147
+ def needs_checkout?
148
+ expected = ref.sha1
149
+ actual = rev_parse('HEAD')
150
+
151
+ ! (expected == actual)
140
152
  end
141
153
  end
142
- end
143
- end
data/lib/r10k/git.rb ADDED
@@ -0,0 +1,14 @@
1
+ module R10K
2
+ module Git
3
+ require 'r10k/git/errors'
4
+
5
+ require 'r10k/git/ref'
6
+ require 'r10k/git/tag'
7
+ require 'r10k/git/head'
8
+ require 'r10k/git/commit'
9
+
10
+ require 'r10k/git/repository'
11
+ require 'r10k/git/cache'
12
+ require 'r10k/git/working_dir'
13
+ end
14
+ end
@@ -1,11 +1,13 @@
1
1
  require 'r10k/module'
2
2
  require 'r10k/errors'
3
3
  require 'r10k/logging'
4
+ require 'r10k/module/metadata'
5
+ require 'r10k/util/subprocess'
6
+ require 'r10k/module_repository/forge'
4
7
 
8
+ require 'pathname'
5
9
  require 'fileutils'
6
- require 'systemu'
7
10
  require 'r10k/semver'
8
- require 'json'
9
11
 
10
12
  class R10K::Module::Forge < R10K::Module::Base
11
13
 
@@ -17,91 +19,156 @@ class R10K::Module::Forge < R10K::Module::Base
17
19
 
18
20
  include R10K::Logging
19
21
 
20
- attr_accessor :owner, :full_name
22
+ # @!attribute [r] author
23
+ # @return [String] The Forge module author
24
+ attr_reader :author
21
25
 
22
- def initialize(name, basedir, args)
23
- @full_name = name
26
+ # @deprecated
27
+ def owner
28
+ logger.warn "#{self.inspect}#owner is deprecated; use #author instead"
29
+ @author
30
+ end
31
+
32
+ # @!attribute [r] full_name
33
+ # @return [String] The fully qualified module name
34
+ attr_reader :full_name
35
+
36
+ def initialize(full_name, basedir, args)
37
+ @full_name = full_name
24
38
  @basedir = basedir
25
39
 
26
- @owner, @name = name.split('/')
40
+ @author, @name = full_name.split('/')
41
+
42
+ @full_path = Pathname.new(File.join(@basedir, @name))
43
+
44
+ @metadata = R10K::Module::Metadata.new(@full_path + 'metadata.json')
27
45
 
28
46
  if args.is_a? String
29
- @version = R10K::SemVer.new(args)
47
+ @expected_version = R10K::SemVer.new(args)
48
+ elsif args.is_a? Symbol and args == :latest
49
+ @expected_version = args
30
50
  end
31
51
  end
32
52
 
33
53
  def sync(options = {})
34
- return if insync?
35
-
36
- if insync?
37
- #logger.debug1 "Module #{@full_name} already matches version #{@version}"
38
- elsif File.exist? metadata_path
39
- #logger.debug "Module #{@full_name} is installed but doesn't match version #{@version}, upgrading"
40
-
41
- # A Pulp based puppetforge http://www.pulpproject.org/ wont support
42
- # `puppet module install abc/xyz --version=v1.5.9` but puppetlabs forge
43
- # will support `puppet module install abc/xyz --version=1.5.9`
44
- #
45
- # Removing v from the semver for constructing the command ensures
46
- # compatibility across both
47
- cmd = []
48
- cmd << 'upgrade'
49
- cmd << "--version=#{@version.to_s.sub(/^v/,'')}" if @version
50
- cmd << "--ignore-dependencies"
51
- cmd << @full_name
52
- pmt cmd
53
- else
54
- FileUtils.mkdir @basedir unless File.directory? @basedir
55
- #logger.debug "Module #{@full_name} is not installed"
56
- cmd = []
57
- cmd << 'install'
58
- cmd << "--version=#{@version.to_s.sub(/^v/,'')}" if @version
59
- cmd << "--ignore-dependencies"
60
- cmd << @full_name
61
- pmt cmd
54
+ case status
55
+ when :absent
56
+ install
57
+ when :outdated
58
+ upgrade
59
+ when :mismatched
60
+ reinstall
62
61
  end
63
62
  end
64
63
 
65
- # @return [R10K::SemVer, NilClass]
66
- def version
67
- if metadata
68
- R10K::SemVer.new(metadata['version'])
69
- else
70
- R10K::SemVer::MIN
64
+ # @return [R10K::SemVer] The expected version that the module
65
+ def expected_version
66
+ if @expected_version == :latest
67
+ set_version_from_forge
71
68
  end
69
+ @expected_version
72
70
  end
73
71
 
74
- def insync?
75
- @version == version
72
+
73
+ # @return [R10K::SemVer] The version of the currently installed module
74
+ def current_version
75
+ @metadata.version
76
76
  end
77
77
 
78
- def metadata
79
- @metadata = JSON.parse(File.read(metadata_path)) rescue nil
78
+ alias version current_version
79
+
80
+ def exist?
81
+ @full_path.exist?
80
82
  end
81
83
 
82
- def metadata_path
83
- File.join(full_path, 'metadata.json')
84
+ def insync?
85
+ status == :insync
86
+ end
87
+
88
+ # Determine the status of the forge module.
89
+ #
90
+ # @return [Symbol] :absent If the directory doesn't exist
91
+ # @return [Symbol] :mismatched If the module is not a forge module, or
92
+ # isn't the right forge module
93
+ # @return [Symbol] :outdated If the installed module is older than expected
94
+ # @return [Symbol] :insync If the module is in the desired state
95
+ def status
96
+ if not self.exist?
97
+ # The module is not installed
98
+ return :absent
99
+ elsif not @metadata.exist?
100
+ # The directory exists but doesn't have a metadata file; it probably
101
+ # isn't a forge module.
102
+ return :mismatched
103
+ end
104
+
105
+ # The module is present and has a metadata file, read the metadata to
106
+ # determine the state of the module.
107
+ @metadata.read
108
+
109
+ if not @author == @metadata.author
110
+ # This is a forge module but the installed module is a different author
111
+ # than the expected author.
112
+ return :mismatched
113
+ end
114
+
115
+ if expected_version && (expected_version != @metadata.version)
116
+ return :outdated
117
+ end
118
+
119
+ return :insync
84
120
  end
85
121
 
86
122
  private
87
123
 
88
- def pmt(args)
89
- cmd = "puppet module --modulepath '#{@basedir}' #{args.join(' ')}"
90
- log_event = "puppet module #{args.join(' ')}, modulepath: #{@basedir.inspect}"
91
- logger.debug1 "Execute: #{cmd}"
124
+ def install
125
+ FileUtils.mkdir @basedir unless File.directory? @basedir
126
+ cmd = []
127
+ cmd << 'install'
128
+ cmd << "--version=#{expected_version}" if expected_version
129
+ cmd << "--ignore-dependencies"
130
+ cmd << @full_name
131
+ pmt cmd
132
+ end
92
133
 
93
- status, stdout, stderr = systemu(cmd)
134
+ def upgrade
135
+ cmd = []
136
+ cmd << 'upgrade'
137
+ cmd << "--version=#{expected_version}" if expected_version
138
+ cmd << "--ignore-dependencies"
139
+ cmd << @full_name
140
+ pmt cmd
141
+ end
94
142
 
95
- logger.debug2 "[#{log_event}] STDOUT: #{stdout.chomp}" unless stdout.empty?
96
- logger.debug2 "[#{log_event}] STDERR: #{stderr.chomp}" unless stderr.empty?
143
+ def uninstall
144
+ FileUtils.rm_rf full_path
145
+ end
97
146
 
98
- unless status == 0
99
- e = R10K::ExecutionFailure.new("#{cmd.inspect} returned with non-zero exit value #{status.inspect}")
100
- e.exit_code = status
101
- e.stdout = stdout
102
- e.stderr = stderr
103
- raise e
104
- end
105
- stdout
147
+ def reinstall
148
+ uninstall
149
+ install
150
+ end
151
+
152
+ # Wrap puppet module commands
153
+ #
154
+ # @param argv [Array<String>]
155
+ #
156
+ # @return [String] The stdout from the executed command
157
+ def pmt(argv)
158
+ argv = ['puppet', 'module', '--modulepath', @basedir] + argv
159
+
160
+ subproc = R10K::Util::Subprocess.new(argv)
161
+ subproc.raise_on_fail = true
162
+ subproc.logger = self.logger
163
+
164
+ result = subproc.execute
165
+
166
+ result.stdout
167
+ end
168
+
169
+ def set_version_from_forge
170
+ repo = R10K::ModuleRepository::Forge.new
171
+ expected = repo.latest_version(@full_name)
172
+ @expected_version = R10K::SemVer.new(expected)
106
173
  end
107
174
  end