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
@@ -1,6 +1,5 @@
1
1
  require 'r10k/module'
2
- require 'r10k/git/working_dir'
3
- require 'forwardable'
2
+ require 'r10k/git'
4
3
 
5
4
  class R10K::Module::Git < R10K::Module::Base
6
5
 
@@ -12,14 +11,17 @@ class R10K::Module::Git < R10K::Module::Base
12
11
  false
13
12
  end
14
13
 
15
- extend Forwardable
16
- def_delegator :@working_dir, :sync
14
+ # @!attribute [r] working_dir
15
+ # @api private
16
+ # @return [R10K::Git::WorkingDir]
17
+ attr_reader :working_dir
17
18
 
18
19
  def initialize(name, basedir, args)
19
20
  @name, @basedir, @args = name, basedir, args
20
21
 
21
- @remote = @args[:git]
22
- @ref = (@args[:ref] || 'master')
22
+ parse_options(args)
23
+
24
+ @full_path = Pathname.new(File.join(basedir, name))
23
25
 
24
26
  @working_dir = R10K::Git::WorkingDir.new(@ref, @remote, @basedir, @name)
25
27
  end
@@ -27,4 +29,68 @@ class R10K::Module::Git < R10K::Module::Base
27
29
  def version
28
30
  @ref
29
31
  end
32
+
33
+ def sync
34
+ case status
35
+ when :absent
36
+ install
37
+ when :mismatched
38
+ uninstall
39
+ install
40
+ when :outdated
41
+ @working_dir.sync
42
+ end
43
+ end
44
+
45
+ def status
46
+ if not @working_dir.exist?
47
+ return :absent
48
+ elsif not @working_dir.git?
49
+ return :mismatched
50
+ elsif not @remote == @working_dir.remote
51
+ return :mismatched
52
+ end
53
+
54
+ if @working_dir.outdated?
55
+ return :outdated
56
+ end
57
+
58
+ return :insync
59
+ end
60
+
61
+ private
62
+
63
+ def install
64
+ @working_dir.sync
65
+ end
66
+
67
+ def uninstall
68
+ @full_path.rmtree
69
+ end
70
+
71
+ def parse_options(options)
72
+ @remote = options.delete(:git)
73
+
74
+ if options[:branch]
75
+ @ref = R10K::Git::Head.new(options.delete(:branch))
76
+ end
77
+
78
+ if options[:tag]
79
+ @ref = R10K::Git::Tag.new(options.delete(:tag))
80
+ end
81
+
82
+ if options[:commit]
83
+ @ref = R10K::Git::Commit.new(options.delete(:commit))
84
+ end
85
+
86
+ if options[:ref]
87
+ @ref = R10K::Git::Ref.new(options.delete(:ref))
88
+ end
89
+
90
+ @ref ||= R10K::Git::Ref.new('master')
91
+
92
+ unless options.empty?
93
+ raise ArgumentError, "Unhandled options #{options.keys.inspect} specified for #{self.class}"
94
+ end
95
+ end
30
96
  end
@@ -0,0 +1,47 @@
1
+ require 'r10k/module'
2
+ require 'r10k/semver'
3
+ require 'json'
4
+
5
+ class R10K::Module::Metadata
6
+
7
+ # @!attribute [r] name
8
+ # @return [String] The module name
9
+ attr_reader :name
10
+
11
+ # @!attribute [r] author
12
+ # @return [String] The module author username
13
+ attr_reader :author
14
+
15
+ # @!attribute [r] version
16
+ # @return [R10K::SemVer] The module version
17
+ attr_reader :version
18
+
19
+ # @param metadata_path [Pathname] The file path to the metadata
20
+ def initialize(metadata_path)
21
+ @metadata_path = metadata_path
22
+
23
+ @version = R10K::SemVer::MIN
24
+ end
25
+
26
+ # Does the metadata file itself exist?
27
+ def exist?
28
+ @metadata_path.file? and @metadata_path.readable?
29
+ end
30
+
31
+ # Attempt to read the metadata file
32
+ def read
33
+ if self.exist?
34
+ hash = JSON.parse(@metadata_path.read)
35
+ attributes_from_hash(hash)
36
+ end
37
+ rescue JSON::ParserError
38
+ false
39
+ end
40
+
41
+ private
42
+
43
+ def attributes_from_hash(json)
44
+ @author, _, @name = json['name'].partition('-')
45
+ @version = R10K::SemVer.new(json['version'])
46
+ end
47
+ end
@@ -0,0 +1,99 @@
1
+ require 'r10k/module'
2
+ require 'r10k/execution'
3
+ require 'r10k/logging'
4
+ require 'r10k/svn/working_dir'
5
+
6
+ class R10K::Module::SVN < R10K::Module::Base
7
+
8
+ R10K::Module.register(self)
9
+
10
+ def self.implement?(name, args)
11
+ args.is_a? Hash and args.has_key? :svn
12
+ end
13
+
14
+ # @!attribute [r] expected_revision
15
+ # @return [String] The SVN revision that the repo should have checked out
16
+ attr_reader :expected_revision
17
+ alias expected_version expected_revision
18
+
19
+ # @!attribute [r] svn_path
20
+ # @return [String] The path inside of the SVN repository to have checked out
21
+ attr_reader :svn_path
22
+
23
+ # @!attribute [r] full_path
24
+ # @return [Pathname] The filesystem path to the SVN repo
25
+ attr_reader :full_path
26
+
27
+ def initialize(name, basedir, args)
28
+ @name = name
29
+ @basedir = basedir
30
+
31
+ parse_options(args)
32
+
33
+ @full_path = Pathname.new(File.join(@basedir, @name))
34
+ @working_dir = R10K::SVN::WorkingDir.new(@full_path)
35
+ end
36
+
37
+ def status
38
+ if not self.exist?
39
+ :absent
40
+ elsif not @working_dir.is_svn?
41
+ :mismatched
42
+ elsif not @url == @working_dir.url
43
+ :mismatched
44
+ elsif not @expected_revision == @working_dir.revision
45
+ :outdated
46
+ else
47
+ :insync
48
+ end
49
+ end
50
+
51
+ def sync
52
+ case status
53
+ when :absent
54
+ install
55
+ when :mismatched
56
+ reinstall
57
+ when :outdated
58
+ update
59
+ end
60
+ end
61
+
62
+ def exist?
63
+ @full_path.exist?
64
+ end
65
+
66
+ private
67
+
68
+ def install
69
+ FileUtils.mkdir @basedir unless File.directory? @basedir
70
+
71
+ @working_dir.checkout(@url, @expected_revision)
72
+ end
73
+
74
+ def uninstall
75
+ @full_path.rmtree
76
+ end
77
+
78
+ def reinstall
79
+ uninstall
80
+ install
81
+ end
82
+
83
+ def update
84
+ @working_dir.update(@expected_revision)
85
+ end
86
+
87
+ def parse_options(hash)
88
+ hash.each_pair do |key, value|
89
+ case key
90
+ when :svn
91
+ @url = value
92
+ when :rev, :revision
93
+ @expected_revision = value
94
+ when :svn_path
95
+ @svn_path = value
96
+ end
97
+ end
98
+ end
99
+ end
data/lib/r10k/module.rb CHANGED
@@ -32,4 +32,5 @@ module R10K::Module
32
32
  require 'r10k/module/base'
33
33
  require 'r10k/module/git'
34
34
  require 'r10k/module/forge'
35
+ require 'r10k/module/svn'
35
36
  end
@@ -0,0 +1,64 @@
1
+ require 'r10k/module_repository'
2
+ require 'r10k/version'
3
+
4
+ require 'faraday'
5
+ require 'faraday_middleware/multi_json'
6
+ require 'faraday_middleware'
7
+
8
+ class R10K::ModuleRepository::Forge
9
+
10
+ # @!attribute [r] forge
11
+ # @return [String] The forge hostname to use for requests
12
+ attr_reader :forge
13
+
14
+ # @!attribute [r] :conn
15
+ # @api private
16
+ # @return [Faraday]
17
+ attr_reader :conn
18
+
19
+ def initialize(forge = 'forge.puppetlabs.com')
20
+ @forge = forge
21
+
22
+ @conn = Faraday.new(
23
+ :url => "https://#{@forge}",
24
+ :user_agent => "Ruby/r10k #{R10K::VERSION}"
25
+ ) do |builder|
26
+ builder.request :multi_json
27
+ builder.response :multi_json
28
+
29
+ # This needs to be _after_ request/response configuration for testing
30
+ # purposes. This comment is the result of much consternation.
31
+ builder.adapter Faraday.default_adapter
32
+ end
33
+ end
34
+
35
+ # Query for all published versions of a module
36
+ #
37
+ # @example
38
+ # forge = R10K::ModuleRepository::Forge.new
39
+ # forge.versions('adrien/boolean')
40
+ # #=> ["0.9.0-rc1", "0.9.0", "1.0.0", "1.0.1"]
41
+ #
42
+ # @param module_name [String] The fully qualified module name
43
+ # @return [Array<String>] All published versions of the given module
44
+ def versions(module_name)
45
+ response = @conn.get("/api/v1/releases.json", {'module' => module_name})
46
+
47
+ response.body[module_name].map do |version_info|
48
+ version_info['version']
49
+ end
50
+ end
51
+
52
+ # Query for the newest published version of a module
53
+ #
54
+ # @example
55
+ # forge = R10K::ModuleRepository::Forge.new
56
+ # forge.latest_version('adrien/boolean')
57
+ # #=> "1.0.1"
58
+ #
59
+ # @param module_name [String] The fully qualified module name
60
+ # @return [String] The latest published version of the given module
61
+ def latest_version(module_name)
62
+ versions(module_name).last
63
+ end
64
+ end
@@ -0,0 +1,8 @@
1
+ module R10K
2
+
3
+ # Locations that can be queried for remote module metadata
4
+ module ModuleRepository
5
+
6
+ require 'r10k/module_repository/forge'
7
+ end
8
+ end
data/lib/r10k/semver.rb CHANGED
@@ -112,7 +112,7 @@ class SemVer < Numeric
112
112
  end
113
113
 
114
114
  def inspect
115
- @vstring || "v#{@major}.#{@minor}.#{@tiny}#{@special}"
115
+ @vstring || "#{@major}.#{@minor}.#{@tiny}#{@special}"
116
116
  end
117
117
  alias :to_s :inspect
118
118
 
@@ -0,0 +1,76 @@
1
+ require 'r10k/logging'
2
+ require 'r10k/util/subprocess'
3
+
4
+ module R10K
5
+ module SVN
6
+ class WorkingDir
7
+
8
+ # @param full_path [Pathname]
9
+ def initialize(full_path)
10
+ @full_path = full_path
11
+ end
12
+
13
+ def is_svn?
14
+ dot_svn = @full_path + '.svn'
15
+ dot_svn.exist?
16
+ end
17
+
18
+ def revision
19
+ info.slice(/^Revision: (\d+)$/, 1)
20
+ end
21
+
22
+ def url
23
+ info.slice(/^URL: (.*)$/, 1)
24
+ end
25
+
26
+ def root
27
+ info.slice(/^Repository Root: (.*)$/, 1)
28
+ end
29
+
30
+ def update(revision = nil)
31
+ argv = %w[update]
32
+ argv << '-r' << revision if revision
33
+
34
+ svn argv, :cwd => @full_path
35
+ end
36
+
37
+ def checkout(url, revision = nil)
38
+ argv = ['checkout', url]
39
+ argv << '-r' << revision if revision
40
+ argv << @full_path.basename.to_s
41
+
42
+ svn argv, :cwd => @full_path.parent
43
+ end
44
+
45
+ private
46
+
47
+ def info
48
+ svn ["info"], :cwd => @full_path
49
+ end
50
+
51
+ include R10K::Execution
52
+ include R10K::Logging
53
+
54
+ # Wrap SVN commands
55
+ #
56
+ # @param argv [Array<String>]
57
+ # @param opts [Hash]
58
+ #
59
+ # @option opts [Pathname] :cwd The directory to run the command in
60
+ #
61
+ # @return [String] The stdout from the given command
62
+ def svn(argv, opts = {})
63
+ argv.unshift('svn')
64
+
65
+ subproc = R10K::Util::Subprocess.new(argv)
66
+ subproc.raise_on_fail = true
67
+ subproc.logger = self.logger
68
+
69
+ subproc.cwd = opts[:cwd]
70
+ result = subproc.execute
71
+
72
+ result.stdout
73
+ end
74
+ end
75
+ end
76
+ end
@@ -11,39 +11,32 @@ module Deployment
11
11
 
12
12
  private
13
13
 
14
- def active_environments(names)
15
-
16
- active = []
17
-
18
- all_environments = @deployment.environments
19
- if names.empty?
20
- active = all_environments
21
- else
22
- # This has average case O(N^2) but N should remain relatively small, so
23
- # while this should be optimized that optimization can wait a while.
24
- names.each do |env_name|
25
-
26
- matching = all_environments.select do |env|
27
- env.dirname == env_name
28
- end
29
-
30
- if matching.empty?
31
- logger.warn "Environment #{env_name} not found in any source"
32
- task_runner.succeeded = false
33
- else
34
- active.concat(matching)
35
- end
36
- end
14
+ def load_environments!
15
+ @environments = @deployment.environments.inject({}) do |hash, env|
16
+ hash[env.dirname] = env
17
+ hash
37
18
  end
38
-
39
- active
40
19
  end
41
20
 
42
21
  # @param [Array<String>] names The list of environments to deploy.
43
22
  #
44
- def with_environments(names = [])
45
- active_environments(names).each do |env|
46
- yield env
23
+ def with_environments(names = [], &block)
24
+ load_environments!
25
+
26
+ # If an explicit list of environments were not given, deploy everything
27
+ if names.size > 0
28
+ to_deploy = names
29
+ else
30
+ to_deploy = @environments.keys
31
+ end
32
+
33
+ to_deploy.reverse.each do |env_name|
34
+ if (env = @environments[env_name])
35
+ yield env
36
+ else
37
+ logger.warn "Environment #{env_name} not found in any source"
38
+ task_runner.succeeded = false
39
+ end
47
40
  end
48
41
  end
49
42
  end