r10k 1.3.5 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.mkd +210 -0
  4. data/CONTRIBUTING.mkd +105 -0
  5. data/Gemfile +2 -6
  6. data/README.mkd +97 -0
  7. data/doc/common-patterns.mkd +44 -0
  8. data/doc/dynamic-environments.mkd +12 -5
  9. data/doc/dynamic-environments/configuration.mkd +16 -1
  10. data/doc/dynamic-environments/{git-environments.markdown → git-environments.mkd} +13 -9
  11. data/doc/dynamic-environments/introduction.mkd +1 -2
  12. data/doc/dynamic-environments/master-configuration.mkd +70 -0
  13. data/doc/dynamic-environments/quickstart.mkd +241 -0
  14. data/doc/dynamic-environments/svn-environments.mkd +45 -0
  15. data/doc/dynamic-environments/usage.mkd +44 -5
  16. data/doc/dynamic-environments/workflow-guide.mkd +247 -0
  17. data/doc/faq.mkd +52 -0
  18. data/doc/puppetfile.mkd +203 -0
  19. data/lib/r10k/action/cri_runner.rb +75 -0
  20. data/lib/r10k/action/deploy.rb +9 -0
  21. data/lib/r10k/action/deploy/display.rb +104 -0
  22. data/lib/r10k/action/deploy/environment.rb +92 -0
  23. data/lib/r10k/action/deploy/module.rb +70 -0
  24. data/lib/r10k/action/puppetfile.rb +10 -0
  25. data/lib/r10k/action/puppetfile/check.rb +41 -0
  26. data/lib/r10k/action/puppetfile/cri_runner.rb +32 -0
  27. data/lib/r10k/action/puppetfile/install.rb +53 -0
  28. data/lib/r10k/action/puppetfile/purge.rb +37 -0
  29. data/lib/r10k/action/runner.rb +36 -0
  30. data/lib/r10k/action/visitor.rb +31 -0
  31. data/lib/r10k/cli/deploy.rb +14 -45
  32. data/lib/r10k/cli/puppetfile.rb +15 -53
  33. data/lib/r10k/deployment.rb +113 -58
  34. data/lib/r10k/deployment/basedir.rb +3 -38
  35. data/lib/r10k/deployment/config.rb +2 -1
  36. data/lib/r10k/deployment/source.rb +2 -0
  37. data/lib/r10k/environment/base.rb +40 -0
  38. data/lib/r10k/environment/git.rb +14 -17
  39. data/lib/r10k/environment/svn.rb +31 -15
  40. data/lib/r10k/errors.rb +33 -22
  41. data/lib/r10k/errors/formatting.rb +28 -0
  42. data/lib/r10k/execution.rb +2 -0
  43. data/lib/r10k/git/cache.rb +1 -6
  44. data/lib/r10k/git/errors.rb +1 -2
  45. data/lib/r10k/git/ref.rb +1 -1
  46. data/lib/r10k/module.rb +1 -1
  47. data/lib/r10k/module/base.rb +94 -2
  48. data/lib/r10k/module/forge.rb +33 -30
  49. data/lib/r10k/module/git.rb +13 -9
  50. data/lib/r10k/module/svn.rb +41 -28
  51. data/lib/r10k/puppetfile.rb +17 -1
  52. data/lib/r10k/semver.rb +2 -0
  53. data/lib/r10k/source/base.rb +8 -0
  54. data/lib/r10k/source/git.rb +1 -1
  55. data/lib/r10k/source/svn.rb +23 -5
  56. data/lib/r10k/svn/remote.rb +23 -3
  57. data/lib/r10k/svn/working_dir.rb +60 -9
  58. data/lib/r10k/task.rb +1 -0
  59. data/lib/r10k/task/deployment.rb +9 -1
  60. data/lib/r10k/task/environment.rb +2 -0
  61. data/lib/r10k/task/module.rb +1 -0
  62. data/lib/r10k/task/puppetfile.rb +3 -0
  63. data/lib/r10k/task_runner.rb +1 -0
  64. data/lib/r10k/util/attempt.rb +84 -0
  65. data/lib/r10k/util/basedir.rb +65 -0
  66. data/lib/r10k/util/purgeable.rb +55 -45
  67. data/lib/r10k/util/setopts.rb +53 -0
  68. data/lib/r10k/util/subprocess.rb +6 -30
  69. data/lib/r10k/util/subprocess/posix/runner.rb +29 -2
  70. data/lib/r10k/util/subprocess/result.rb +17 -4
  71. data/lib/r10k/util/subprocess/subprocess_error.rb +24 -0
  72. data/lib/r10k/version.rb +1 -1
  73. data/r10k.gemspec +7 -29
  74. data/spec/fixtures/unit/puppetfile/invalid-syntax/Puppetfile +1 -0
  75. data/spec/fixtures/unit/puppetfile/load-error/Puppetfile +1 -0
  76. data/spec/matchers/exit_with.rb +28 -0
  77. data/spec/r10k-mocks.rb +3 -0
  78. data/spec/r10k-mocks/mock_config.rb +28 -0
  79. data/spec/r10k-mocks/mock_env.rb +7 -0
  80. data/spec/r10k-mocks/mock_source.rb +10 -0
  81. data/spec/shared-examples/git-ref.rb +7 -7
  82. data/spec/spec_helper.rb +17 -5
  83. data/spec/unit/action/cri_runner_spec.rb +76 -0
  84. data/spec/unit/action/puppetfile/cri_action_spec.rb +65 -0
  85. data/spec/unit/action/runner_spec.rb +64 -0
  86. data/spec/unit/action/visitor_spec.rb +39 -0
  87. data/spec/unit/deployment_spec.rb +142 -0
  88. data/spec/unit/environment/base_spec.rb +38 -0
  89. data/spec/unit/environment/git_spec.rb +40 -10
  90. data/spec/unit/environment/svn_spec.rb +41 -4
  91. data/spec/unit/errors/formatting_spec.rb +84 -0
  92. data/spec/unit/git/alternates_spec.rb +1 -1
  93. data/spec/unit/git/head_spec.rb +1 -1
  94. data/spec/unit/git/ref_spec.rb +1 -1
  95. data/spec/unit/git/working_dir_spec.rb +1 -1
  96. data/spec/unit/module/base_spec.rb +72 -0
  97. data/spec/unit/module/forge_spec.rb +49 -8
  98. data/spec/unit/module/git_spec.rb +78 -0
  99. data/spec/unit/module/svn_spec.rb +40 -4
  100. data/spec/unit/module_spec.rb +3 -3
  101. data/spec/unit/puppetfile_spec.rb +84 -0
  102. data/spec/unit/settings/container_spec.rb +1 -1
  103. data/spec/unit/source/base_spec.rb +31 -0
  104. data/spec/unit/source/git_spec.rb +7 -7
  105. data/spec/unit/source/svn_spec.rb +1 -1
  106. data/spec/unit/svn/working_dir_spec.rb +56 -0
  107. data/spec/unit/util/attempt_spec.rb +82 -0
  108. data/spec/unit/util/setopts_spec.rb +59 -0
  109. data/spec/unit/util/subprocess/result_spec.rb +36 -0
  110. data/spec/unit/util/subprocess/subprocess_error_spec.rb +26 -0
  111. data/spec/unit/util/subprocess_spec.rb +2 -7
  112. metadata +83 -100
  113. data/.nodeset.yml +0 -7
  114. data/.rspec +0 -1
  115. data/README.markdown +0 -276
  116. data/Rakefile +0 -1
  117. data/doc/puppetfile.markdown +0 -87
  118. data/spec/rspec-system-r10k/puppetfile.rb +0 -24
  119. data/spec/rspec-system-r10k/tmpdir.rb +0 -32
  120. data/spec/system-provisioning/el.rb +0 -38
  121. data/spec/system/module/forge/install_spec.rb +0 -51
  122. data/spec/system/module/git/install_spec.rb +0 -117
  123. data/spec/system/module/svn/install_spec.rb +0 -51
  124. data/spec/system/module/svn/update_spec.rb +0 -38
  125. data/spec/system/spec_helper.rb +0 -60
  126. data/spec/system/system-helpers.rb +0 -4
  127. data/spec/system/version_spec.rb +0 -7
@@ -1,39 +1,4 @@
1
- require 'r10k/git/cache'
2
- require 'r10k/deployment/environment'
3
- require 'r10k/util/purgeable'
1
+ require 'r10k/util/basedir'
4
2
 
5
- module R10K
6
- class Deployment
7
-
8
- # Represents a directory containing environments
9
- # @api private
10
- class Basedir
11
-
12
- def initialize(path,deployment)
13
- @path = path
14
- @deployment = deployment
15
- end
16
-
17
- include R10K::Util::Purgeable
18
-
19
- # Return the path of the basedir
20
- # @note This implements a required method for the Purgeable mixin
21
- # @return [String]
22
- def managed_directory
23
- @path
24
- end
25
-
26
- # List all environments that should exist in this basedir
27
- # @note This implements a required method for the Purgeable mixin
28
- # @return [Array<String>]
29
- def desired_contents
30
- @deployment.sources.inject([])do |list, source|
31
- if source.managed_directory == @path
32
- list += source.desired_contents
33
- end
34
- list
35
- end
36
- end
37
- end
38
- end
39
- end
3
+ # @deprecated
4
+ R10K::Deployment::Basedir = R10K::Util::Basedir
@@ -1,5 +1,6 @@
1
1
  require 'r10k/deployment'
2
2
  require 'r10k/deployment/config/loader'
3
+ require 'yaml'
3
4
 
4
5
  module R10K
5
6
  class Deployment
@@ -42,7 +43,7 @@ class Config
42
43
  end
43
44
  end
44
45
  begin
45
- @config = YAML.load_file(@configfile)
46
+ @config = ::YAML.load_file(@configfile)
46
47
  apply_config_settings
47
48
  rescue => e
48
49
  raise ConfigError, "Couldn't load config file: #{e.message}"
@@ -3,6 +3,7 @@ require 'r10k/util/core_ext/hash_ext'
3
3
 
4
4
  module R10K
5
5
  class Deployment
6
+ # :nocov:
6
7
  class Source
7
8
  # Create a new source from a hash representation
8
9
  #
@@ -34,4 +35,5 @@ class Source
34
35
  end
35
36
  end
36
37
  end
38
+ # :nocov:
37
39
  end
@@ -15,6 +15,15 @@ class R10K::Environment::Base
15
15
  # @return [String] The directory name for the given environment
16
16
  attr_reader :dirname
17
17
 
18
+ # @!attribute [r] path
19
+ # @return [Pathname] The full path to the given environment
20
+ attr_reader :path
21
+
22
+ # @!attribute [r] puppetfile
23
+ # @api public
24
+ # @return [R10K::Puppetfile] The puppetfile instance associated with this environment
25
+ attr_reader :puppetfile
26
+
18
27
  # Initialize the given environment.
19
28
  #
20
29
  # @param name [String] The unique name describing this environment.
@@ -29,6 +38,8 @@ class R10K::Environment::Base
29
38
  @options = options
30
39
 
31
40
  @full_path = File.join(@basedir, @dirname)
41
+ @path = Pathname.new(File.join(@basedir, @dirname))
42
+ @puppetfile = R10K::Puppetfile.new(@full_path)
32
43
  end
33
44
 
34
45
  # Synchronize the given environment.
@@ -39,4 +50,33 @@ class R10K::Environment::Base
39
50
  def sync
40
51
  raise NotImplementedError, "#{self.class} has not implemented method #{__method__}"
41
52
  end
53
+
54
+ # Determine the current status of the environment.
55
+ #
56
+ # This can return the following values:
57
+ #
58
+ # * :absent - there is no module installed
59
+ # * :mismatched - there is a module installed but it must be removed and reinstalled
60
+ # * :outdated - the correct module is installed but it needs to be updated
61
+ # * :insync - the correct module is installed and up to date, or the module is actually a boy band.
62
+ #
63
+ # @api public
64
+ # @abstract
65
+ # @return [Symbol]
66
+ def status
67
+ raise NotImplementedError, "#{self.class} has not implemented method #{__method__}"
68
+ end
69
+
70
+ # @return [Array<R10K::Module::Base>] All modules defined in the Puppetfile
71
+ # associated with this environment.
72
+ def modules
73
+ @puppetfile.load
74
+ @puppetfile.modules
75
+ end
76
+
77
+ def accept(visitor)
78
+ visitor.visit(:environment, self) do
79
+ puppetfile.accept(visitor)
80
+ end
81
+ end
42
82
  end
@@ -22,11 +22,6 @@ class R10K::Environment::Git < R10K::Environment::Base
22
22
  # @return [R10K::Git::WorkingDir] The git working directory backing this environment
23
23
  attr_reader :working_dir
24
24
 
25
- # @!attribute [r] puppetfile
26
- # @api public
27
- # @return [R10K::Puppetfile] The puppetfile instance associated with this environment
28
- attr_reader :puppetfile
29
-
30
25
  # Initialize the given SVN environment.
31
26
  #
32
27
  # @param name [String] The unique name describing this environment.
@@ -42,7 +37,6 @@ class R10K::Environment::Git < R10K::Environment::Base
42
37
  @ref = options[:ref]
43
38
 
44
39
  @working_dir = R10K::Git::WorkingDir.new(@ref, @remote, @basedir, @dirname)
45
- @puppetfile = R10K::Puppetfile.new(@full_path)
46
40
  end
47
41
 
48
42
  # Clone or update the given Git environment.
@@ -53,15 +47,25 @@ class R10K::Environment::Git < R10K::Environment::Base
53
47
  # @api public
54
48
  # @return [void]
55
49
  def sync
56
- recursive_needed = !(@working_dir.cloned?)
57
50
  @working_dir.sync
51
+ @synced = true
52
+ end
58
53
 
59
- if recursive_needed
60
- logger.debug "Environment #{@full_path} is a fresh clone; automatically updating modules."
61
- sync_modules
54
+ def status
55
+ if !@working_dir.exist?
56
+ :absent
57
+ elsif !@working_dir.git?
58
+ :mismatched
59
+ elsif !(@remote == @working_dir.remote)
60
+ :mismatched
61
+ elsif !@synced
62
+ :outdated
63
+ else
64
+ :insync
62
65
  end
63
66
  end
64
67
 
68
+ # @deprecated
65
69
  # @api private
66
70
  def sync_modules
67
71
  modules.each do |mod|
@@ -69,11 +73,4 @@ class R10K::Environment::Git < R10K::Environment::Base
69
73
  mod.sync
70
74
  end
71
75
  end
72
-
73
- # @return [Array<R10K::Module::Base>] All modules defined in the Puppetfile
74
- # associated with this environment.
75
- def modules
76
- @puppetfile.load
77
- @puppetfile.modules
78
- end
79
76
  end
@@ -1,5 +1,6 @@
1
1
  require 'r10k/puppetfile'
2
2
  require 'r10k/svn/working_dir'
3
+ require 'r10k/util/setopts'
3
4
 
4
5
  # This class implements an environment based on an SVN branch.
5
6
  #
@@ -17,10 +18,17 @@ class R10K::Environment::SVN < R10K::Environment::Base
17
18
  # @return [R10K::SVN::WorkingDir] The SVN working directory backing this environment
18
19
  attr_reader :working_dir
19
20
 
20
- # @!attribute [r] puppetfile
21
- # @api public
22
- # @return [R10K::Puppetfile] The puppetfile instance associated with this environment
23
- attr_reader :puppetfile
21
+ # @!attribute [r] username
22
+ # @return [String, nil] The SVN username to be passed to the underlying SVN commands
23
+ # @api private
24
+ attr_reader :username
25
+
26
+ # @!attribute [r] password
27
+ # @return [String, nil] The SVN password to be passed to the underlying SVN commands
28
+ # @api private
29
+ attr_reader :password
30
+
31
+ include R10K::Util::Setopts
24
32
 
25
33
  # Initialize the given SVN environment.
26
34
  #
@@ -29,14 +37,15 @@ class R10K::Environment::SVN < R10K::Environment::Base
29
37
  # @param dirname [String] The directory name for this environment.
30
38
  # @param options [Hash] An additional set of options for this environment.
31
39
  #
32
- # @param options [String] :remote The URL to the remote SVN branch to check out
40
+ # @option options [String] :remote The URL to the remote SVN branch to check out
41
+ # @option options [String] :username The SVN username
42
+ # @option options [String] :password The SVN password
33
43
  def initialize(name, basedir, dirname, options = {})
34
44
  super
35
45
 
36
- @remote = options[:remote]
46
+ setopts(options, {:remote => :self, :username => :self, :password => :self})
37
47
 
38
- @working_dir = R10K::SVN::WorkingDir.new(Pathname.new(@full_path))
39
- @puppetfile = R10K::Puppetfile.new(@full_path)
48
+ @working_dir = R10K::SVN::WorkingDir.new(Pathname.new(@full_path), :username => @username, :password => @password)
40
49
  end
41
50
 
42
51
  # Perform an initial checkout of the SVN repository or update the repository.
@@ -51,18 +60,25 @@ class R10K::Environment::SVN < R10K::Environment::Base
51
60
  @working_dir.update
52
61
  else
53
62
  @working_dir.checkout(@remote)
54
- logger.debug "Environment #{@full_path} is a fresh clone; automatically updating modules."
55
- sync_modules
56
63
  end
64
+ @synced = true
57
65
  end
58
66
 
59
- # @return [Array<R10K::Module::Base>] All modules defined in the Puppetfile
60
- # associated with this environment.
61
- def modules
62
- @puppetfile.load
63
- @puppetfile.modules
67
+ def status
68
+ if !@path.exist?
69
+ :absent
70
+ elsif !@working_dir.is_svn?
71
+ :mismatched
72
+ elsif !(@remote == @working_dir.url)
73
+ :mismatched
74
+ elsif !@synced
75
+ :outdated
76
+ else
77
+ :insync
78
+ end
64
79
  end
65
80
 
81
+ # @deprecated
66
82
  # @api private
67
83
  def sync_modules
68
84
  modules.each do |mod|
@@ -1,32 +1,43 @@
1
+ require 'r10k'
2
+
1
3
  module R10K
4
+
5
+ # @deprecated
2
6
  class ExecutionFailure < StandardError
3
7
  attr_accessor :exit_code, :stdout, :stderr
4
8
  end
5
9
 
6
- # An error class that accepts an optional hash.
7
- #
8
- # @overload initialize(mesg)
9
- # @param mesg [String] The exception mesg
10
- #
11
- # @overload initialize(mesg, options)
12
- # @param mesg [String] The exception mesg
13
- # @param options [Hash] A set of options to store on the exception
14
- #
15
- # @overload initialize(options)
16
- # @param options [Hash] A set of options to store on the exception
10
+ # An error class that accepts an optional hash and wrapped error message
17
11
  #
18
- class R10KError < StandardError
19
- def initialize(mesg = nil, options = {})
20
- if mesg.is_a? String
21
- super(mesg)
22
- @mesg = mesg
23
- @options = options
24
- elsif mesg.is_a? Hash
25
- @mesg = nil
26
- @options = mesg
27
- elsif mesg.nil? and options
28
- @options = options
12
+ class Error < StandardError
13
+ attr_accessor :original
14
+
15
+ # Generate a wrapped exception
16
+ #
17
+ # @param original [Exception] The exception to wrap
18
+ # @param mesg [String]
19
+ # @param options [Hash]
20
+ #
21
+ # @return [R10K::Error]
22
+ def self.wrap(original, mesg, options = {})
23
+ new(mesg, options).tap do |e|
24
+ e.set_backtrace(caller(4))
25
+ e.original = original
29
26
  end
30
27
  end
28
+
29
+ # @overload initialize(mesg)
30
+ # @param mesg [String] The exception mesg
31
+ #
32
+ # @overload initialize(mesg, options)
33
+ # @param mesg [String] The exception mesg
34
+ # @param options [Hash] A set of options to store on the exception
35
+ def initialize(mesg, options = {})
36
+ super(mesg)
37
+ @options = options
38
+ end
31
39
  end
40
+
41
+ # @deprecated
42
+ R10KError = Error
32
43
  end
@@ -0,0 +1,28 @@
1
+ require 'r10k/errors'
2
+
3
+ module R10K
4
+ module Errors
5
+ module Formatting
6
+ module_function
7
+
8
+ # Format this exception for displaying to the user
9
+ #
10
+ # @param exc [Exception] The exception to format
11
+ # @param with_backtrace [true, false] Whether the backtrace should be
12
+ # included with this exception
13
+ # @return [String]
14
+ def format_exception(exc, with_backtrace = false)
15
+ lines = []
16
+ lines << exc.message
17
+ if with_backtrace
18
+ lines.concat(exc.backtrace)
19
+ end
20
+ if exc.respond_to?(:original) && exc.original
21
+ lines << "Original exception:"
22
+ lines<< format_exception(exc.original, with_backtrace)
23
+ end
24
+ lines.join("\n")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,6 +3,7 @@ require 'r10k/errors'
3
3
  require 'systemu'
4
4
 
5
5
  module R10K
6
+ # :nocov:
6
7
  module Execution
7
8
  include R10K::Logging
8
9
 
@@ -42,4 +43,5 @@ module Execution
42
43
  stdout
43
44
  end
44
45
  end
46
+ # :nocov:
45
47
  end
@@ -68,12 +68,7 @@ class R10K::Git::Cache < R10K::Git::Repository
68
68
  git ['clone', '--mirror', @remote, git_dir]
69
69
  end
70
70
  rescue R10K::Util::Subprocess::SubprocessError => e
71
- msg = e.result.stderr.slice(/^fatal: .*$/)
72
- if msg
73
- raise R10K::Git::GitError, "Couldn't update git cache for #{@remote}: #{msg.inspect}"
74
- else
75
- raise e
76
- end
71
+ raise R10K::Git::GitError.wrap(e, "Couldn't update git cache for #{@remote}")
77
72
  end
78
73
 
79
74
  # @return [Array<String>] A list the branches for the git repository
@@ -3,8 +3,7 @@ require 'r10k/errors'
3
3
  module R10K
4
4
  module Git
5
5
 
6
- class GitError < R10KError
7
- end
6
+ class GitError < R10K::Error; end
8
7
 
9
8
  class UnresolvableRefError < GitError
10
9
 
@@ -48,7 +48,7 @@ class R10K::Git::Ref
48
48
  end
49
49
 
50
50
  def ==(other)
51
- other.sha1 == self.sha1
51
+ other.is_a?(R10K::Git::Ref) && other.sha1 == self.sha1
52
52
  rescue ArgumentError, R10K::Git::UnresolvableRefError
53
53
  false
54
54
  end
@@ -31,6 +31,6 @@ module R10K::Module
31
31
 
32
32
  require 'r10k/module/base'
33
33
  require 'r10k/module/git'
34
- require 'r10k/module/forge'
35
34
  require 'r10k/module/svn'
35
+ require 'r10k/module/forge'
36
36
  end
@@ -1,10 +1,102 @@
1
1
  require 'r10k/module'
2
2
 
3
+ # This class defines a common interface for module implementations.
3
4
  class R10K::Module::Base
4
- attr_accessor :name, :basedir
5
5
 
6
+ # @!attribute [r] title
7
+ # @return [String] The forward slash separated owner and name of the module
8
+ attr_reader :title
9
+
10
+ # @!attribute [r] name
11
+ # @return [String] The name of the module
12
+ attr_reader :name
13
+
14
+ # @param [r] dirname
15
+ # @return [String] The name of the directory containing this module
16
+ attr_reader :dirname
17
+
18
+ # @deprecated
19
+ alias :basedir :dirname
20
+
21
+ # @!attribute [r] owner
22
+ # @return [String, nil] The owner of the module if one is specified
23
+ attr_reader :owner
24
+
25
+ # @!attribute [r] path
26
+ # @return [Pathname] The full path of the module
27
+ attr_reader :path
28
+
29
+ # There's been some churn over `author` vs `owner` and `full_name` over
30
+ # `title`, so in the short run it's easier to support both and deprecate one
31
+ # later.
32
+ alias :author :owner
33
+ alias :full_name :title
34
+
35
+ # @param title [String]
36
+ # @param dirname [String]
37
+ # @param args [Array]
38
+ def initialize(title, dirname, args)
39
+ @title = title
40
+ @dirname = dirname
41
+ @args = args
42
+ @owner, @name = parse_title(title)
43
+ @path = Pathname.new(File.join(@dirname, @name))
44
+ end
45
+
46
+ # @deprecated
6
47
  # @return [String] The full filesystem path to the module.
7
48
  def full_path
8
- File.join(@basedir, @name)
49
+ path.to_s
50
+ end
51
+
52
+ # Synchronize this module with the indicated state.
53
+ # @abstract
54
+ def sync
55
+ raise NotImplementedError
56
+ end
57
+
58
+ # Return the desired version of this module
59
+ # @abstract
60
+ def version
61
+ raise NotImplementedError
62
+ end
63
+
64
+ # Return the status of the currently installed module.
65
+ #
66
+ # This can return the following values:
67
+ #
68
+ # * :absent - there is no module installed
69
+ # * :mismatched - there is a module installed but it must be removed and reinstalled
70
+ # * :outdated - the correct module is installed but it needs to be updated
71
+ # * :insync - the correct module is installed and up to date, or the module is actually a boy band.
72
+ #
73
+ # @return [Symbol]
74
+ # @abstract
75
+ def status
76
+ raise NotImplementedError
77
+ end
78
+
79
+ def accept(visitor)
80
+ visitor.visit(:module, self)
81
+ end
82
+
83
+ # Return the properties of the module
84
+ #
85
+ # @return [Hash]
86
+ # @abstract
87
+ def properties
88
+ raise NotImplementedError
89
+ end
90
+
91
+ private
92
+
93
+ def parse_title(title)
94
+ if (match = title.match(/\A(\w+)\Z/))
95
+ [nil, match[1]]
96
+ elsif (match = title.match(/\A(\w+)[-\/](\w+)\Z/))
97
+ [match[1], match[2]]
98
+ else
99
+ raise ArgumentError, "Module names must match either 'modulename' or 'owner/modulename'"
100
+ end
9
101
  end
10
102
  end