r10k 1.1.4 → 1.2.0rc1

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