r10k 0.0.9 → 1.0.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 (54) hide show
  1. data/bin/r10k +1 -1
  2. data/lib/r10k.rb +0 -4
  3. data/lib/r10k/cli.rb +9 -5
  4. data/lib/r10k/cli/deploy.rb +108 -0
  5. data/lib/r10k/cli/environment.rb +5 -1
  6. data/lib/r10k/cli/environment/deploy.rb +6 -28
  7. data/lib/r10k/cli/environment/list.rb +6 -10
  8. data/lib/r10k/cli/environment/stale.rb +6 -16
  9. data/lib/r10k/cli/module.rb +5 -1
  10. data/lib/r10k/cli/module/deploy.rb +5 -32
  11. data/lib/r10k/cli/module/list.rb +6 -27
  12. data/lib/r10k/cli/puppetfile.rb +76 -0
  13. data/lib/r10k/cli/synchronize.rb +8 -24
  14. data/lib/r10k/cli/version.rb +22 -0
  15. data/lib/r10k/deployment.rb +55 -26
  16. data/lib/r10k/deployment/config.rb +69 -0
  17. data/lib/r10k/{config → deployment/config}/loader.rb +9 -5
  18. data/lib/r10k/deployment/environment.rb +88 -0
  19. data/lib/r10k/deployment/source.rb +79 -0
  20. data/lib/r10k/errors.rb +3 -5
  21. data/lib/r10k/execution.rb +43 -0
  22. data/lib/r10k/git/cache.rb +131 -0
  23. data/lib/r10k/git/errors.rb +34 -0
  24. data/lib/r10k/git/repository.rb +74 -0
  25. data/lib/r10k/git/working_dir.rb +142 -0
  26. data/lib/r10k/logging.rb +6 -2
  27. data/lib/r10k/module.rb +10 -13
  28. data/lib/r10k/module/forge.rb +35 -22
  29. data/lib/r10k/module/git.rb +18 -8
  30. data/lib/r10k/puppetfile.rb +107 -0
  31. data/lib/r10k/task.rb +13 -0
  32. data/lib/r10k/task/deployment.rb +151 -0
  33. data/lib/r10k/task/environment.rb +29 -0
  34. data/lib/r10k/task/module.rb +18 -0
  35. data/lib/r10k/task/puppetfile.rb +99 -0
  36. data/lib/r10k/task_runner.rb +72 -0
  37. data/lib/r10k/util/purgeable.rb +50 -0
  38. data/lib/r10k/version.rb +1 -1
  39. data/spec/unit/deployment/environment_spec.rb +19 -0
  40. data/spec/unit/git/cache_spec.rb +37 -0
  41. data/spec/unit/git/working_dir_spec.rb +15 -0
  42. data/spec/unit/module/forge_spec.rb +95 -0
  43. data/spec/unit/module_spec.rb +29 -0
  44. metadata +79 -44
  45. data/lib/r10k/action.rb +0 -7
  46. data/lib/r10k/action/environment.rb +0 -74
  47. data/lib/r10k/action/module.rb +0 -36
  48. data/lib/r10k/cli/cache.rb +0 -32
  49. data/lib/r10k/config.rb +0 -46
  50. data/lib/r10k/deployment/environment_collection.rb +0 -75
  51. data/lib/r10k/librarian.rb +0 -31
  52. data/lib/r10k/librarian/dsl.rb +0 -20
  53. data/lib/r10k/root.rb +0 -98
  54. data/lib/r10k/synchro/git.rb +0 -226
@@ -0,0 +1,43 @@
1
+ require 'r10k/logging'
2
+ require 'r10k/errors'
3
+
4
+ require 'systemu'
5
+
6
+ module R10K
7
+ module Execution
8
+ include R10K::Logging
9
+
10
+ # Execute a command and return stdout
11
+ #
12
+ # @params [String] cmd
13
+ # @params [Hash] opts
14
+ #
15
+ # @option opts [String] :event An optional log event name. Defaults to cmd.
16
+ #
17
+ # @raise [R10K::ExecutionFailure] If the executed command exited with a
18
+ # nonzero exit code.
19
+ #
20
+ # @return [String] the stdout from the command
21
+ def execute(cmd, opts = {})
22
+
23
+ event = opts[:event] || cmd
24
+
25
+ logger.debug1 "Execute: #{event.inspect}"
26
+
27
+ status, stdout, stderr = systemu(cmd)
28
+
29
+ logger.debug2 "[#{event}] STDOUT: #{stdout.chomp}" unless stdout.empty?
30
+ logger.debug2 "[#{event}] STDERR: #{stderr.chomp}" unless stderr.empty?
31
+
32
+ unless status == 0
33
+ msg = "#{cmd.inspect} returned with non-zero exit value #{status.exitstatus}"
34
+ e = R10K::ExecutionFailure.new(msg)
35
+ e.exit_code = status
36
+ e.stdout = stdout
37
+ e.stderr = stderr
38
+ raise e
39
+ end
40
+ stdout
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,131 @@
1
+ require 'r10k/logging'
2
+ require 'r10k/git/repository'
3
+
4
+ module R10K
5
+ module Git
6
+ class Cache < R10K::Git::Repository
7
+ # Mirror a git repository for use shared git object repositories
8
+ #
9
+ # @see man git-clone(1)
10
+
11
+ class << self
12
+
13
+ # @!attribute [r] cache_root
14
+ # @return [String] The directory to use as the cache.
15
+ attr_writer :cache_root
16
+
17
+ def cache_root
18
+ @cache_root
19
+ end
20
+
21
+ # Memoize class instances and return existing instances.
22
+ #
23
+ # This allows objects to mark themselves as cached to prevent unnecessary
24
+ # cache refreshes.
25
+ #
26
+ # @param [String] remote A git remote URL
27
+ # @return [R10K::Synchro::Git]
28
+ def new(remote)
29
+ @repos ||= {}
30
+ unless @repos[remote]
31
+ obj = self.allocate
32
+ obj.send(:initialize, remote)
33
+ @repos[remote] = obj
34
+ end
35
+ @repos[remote]
36
+ end
37
+
38
+ def clear!
39
+ @repos = {}
40
+ end
41
+ end
42
+
43
+ include R10K::Logging
44
+
45
+ # @!attribute [r] remote
46
+ # @return [String] The git repository remote
47
+ attr_reader :remote
48
+
49
+ # @!attribute [r] cache_root
50
+ # Where to keep the git object cache. Defaults to ~/.r10k/git if a class
51
+ # level value is not set.
52
+ # @return [String] The directory to use as the cache
53
+ attr_reader :cache_root
54
+
55
+ # @!attribute [r] path
56
+ # @return [String] The path to the git cache repository
57
+ attr_reader :path
58
+
59
+ # @param [String] remote
60
+ # @param [String] cache_root
61
+ def initialize(remote)
62
+ @remote = remote
63
+
64
+ @cache_root = self.class.cache_root || default_cache_root
65
+
66
+ @path = File.join(@cache_root, sanitized_dirname)
67
+ end
68
+
69
+ def sync
70
+ if @synced
71
+ # XXX This gets really spammy. Might be good to turn it on later, but for
72
+ # general work it's way much.
73
+ #logger.debug "#{@remote} already synced this run, not syncing again"
74
+ else
75
+ sync!
76
+ @synced = true
77
+ end
78
+ end
79
+
80
+ def sync!
81
+ if cached?
82
+ # XXX This gets really spammy. Might be good to turn it on later, but for
83
+ # general work it's way much.
84
+ #logger.debug "Updating existing cache at #{@path}"
85
+ git "fetch --prune", :git_dir => @path
86
+ else
87
+ logger.debug "Creating new git cache for #{@remote.inspect}"
88
+ FileUtils.mkdir_p cache_root unless File.exist? @cache_root
89
+ git "clone --mirror #{@remote} #{@path}"
90
+ end
91
+ end
92
+
93
+ # @return [Array<String>] A list the branches for the git repository
94
+ def branches
95
+ output = git "branch", :git_dir => @path
96
+ output.split("\n").map do |str|
97
+ # the `git branch` command returns output like this:
98
+ # <pre>
99
+ # 0.11.x
100
+ # 0.12.x
101
+ # * master
102
+ # passenger_scoping
103
+ # </pre>
104
+ #
105
+ # The string index notation strips off the leading whitespace/asterisk
106
+ str[2..-1]
107
+ end
108
+ end
109
+
110
+ # @return [true, false] If the repository has been locally cached
111
+ def cached?
112
+ File.exist? @path
113
+ end
114
+
115
+ private
116
+
117
+ # Reformat the remote name into something that can be used as a directory
118
+ def sanitized_dirname
119
+ @remote.gsub(/[^@\w\.-]/, '-')
120
+ end
121
+
122
+ def default_cache_root
123
+ File.expand_path('~/.r10k/git')
124
+ end
125
+
126
+ def git_dir
127
+ @path
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,34 @@
1
+ module R10K
2
+ module Git
3
+ class NonexistentHashError < StandardError
4
+ # Raised when a hash was requested that can't be found in the repository
5
+
6
+ attr_reader :hash
7
+ attr_reader :git_dir
8
+
9
+ def initialize(msg = nil, git_dir = nil)
10
+ super(msg)
11
+
12
+ @git_dir = git_dir
13
+ end
14
+
15
+ HASHLIKE = %r[[A-Fa-f0-9]]
16
+
17
+ # Print a friendly error message if an object hash is given as the message
18
+ def message
19
+ msg = super
20
+ if msg and msg.match(HASHLIKE)
21
+ msg = "Could not locate hash #{msg.inspect} in repository"
22
+ elsif msg.nil?
23
+ msg = "Could not locate hash in repository"
24
+ end
25
+
26
+ if @git_dir
27
+ msg << " at #{@git_dir}. (Does the remote repository need to be updated?)"
28
+ end
29
+
30
+ msg
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,74 @@
1
+ require 'r10k/execution'
2
+ require 'r10k/git/errors'
3
+
4
+ module R10K
5
+ module Git
6
+ class Repository
7
+ # Define an abstract base class for git repositories.
8
+
9
+ include R10K::Execution
10
+
11
+ # @!attribute [r] remote
12
+ # @return [String] The URL to the git repository
13
+ attr_reader :remote
14
+
15
+ # @!attribute [r] basedir
16
+ # @return [String] The basedir for the working directory
17
+ attr_reader :basedir
18
+
19
+ # @!attribute [r] dirname
20
+ # @return [String] The name for the directory
21
+ attr_reader :dirname
22
+
23
+ # Resolve a ref to a commit hash
24
+ #
25
+ # @param [String] ref
26
+ #
27
+ # @return [String] The dereferenced hash of `ref`
28
+ def rev_parse(ref)
29
+ commit = git "rev-parse #{ref}^{commit}", :git_dir => git_dir
30
+ commit.chomp
31
+ rescue R10K::ExecutionFailure
32
+ raise R10K::Git::NonexistentHashError.new(ref, git_dir)
33
+ end
34
+
35
+ # Wrap git commands
36
+ #
37
+ # @param [String] command_line_args The arguments for the git prompt
38
+ # @param [Hash] opts
39
+ #
40
+ # @option opts [String] :git_dir
41
+ # @option opts [String] :work_tree
42
+ # @option opts [String] :work_tree
43
+ #
44
+ # @raise [R10K::ExecutionFailure] If the executed command exited with a
45
+ # nonzero exit code.
46
+ #
47
+ # @return [String] The git command output
48
+ def git(command_line_args, opts = {})
49
+ args = %w{git}
50
+
51
+ log_event = "git #{command_line_args}"
52
+ log_event << ", args: #{opts.inspect}" unless opts.empty?
53
+
54
+
55
+ if opts[:path]
56
+ args << "--git-dir #{opts[:path]}/.git"
57
+ args << "--work-tree #{opts[:path]}"
58
+ else
59
+ if opts[:git_dir]
60
+ args << "--git-dir #{opts[:git_dir]}"
61
+ end
62
+ if opts[:work_tree]
63
+ args << "--work-tree #{opts[:work_tree]}"
64
+ end
65
+ end
66
+
67
+ args << command_line_args
68
+ cmd = args.join(' ')
69
+
70
+ execute(cmd, :event => log_event)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,142 @@
1
+ require 'forwardable'
2
+ require 'r10k/logging'
3
+ require 'r10k/git/cache'
4
+
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.
13
+
14
+ include R10K::Logging
15
+
16
+ extend Forwardable
17
+
18
+ # @!attribute [r] cache
19
+ # @return [R10K::Git::Cache] The object cache backing this working directory
20
+ attr_reader :cache
21
+
22
+ # @!attribute [r] ref
23
+ # @return [String] The git reference to use check out in the given directory
24
+ attr_reader :ref
25
+
26
+ # Instantiates a new git synchro and optionally prepares for caching
27
+ #
28
+ # @param [String] ref
29
+ # @param [String] remote
30
+ # @param [String] basedir
31
+ # @param [String] dirname
32
+ def initialize(ref, remote, basedir, dirname = nil)
33
+ @ref = ref
34
+ @remote = remote
35
+ @basedir = basedir
36
+ @dirname = dirname || ref
37
+
38
+ @full_path = File.join(@basedir, @dirname)
39
+
40
+ @cache = R10K::Git::Cache.new(@remote)
41
+ end
42
+
43
+ # Synchronize the local git repository.
44
+ def sync
45
+ # TODO stop forcing a sync every time.
46
+ @cache.sync
47
+
48
+ if cloned?
49
+ fetch
50
+ else
51
+ clone
52
+ end
53
+ reset
54
+ end
55
+
56
+ # Determine if repo has been cloned into a specific dir
57
+ #
58
+ # @return [true, false] If the repo has already been cloned
59
+ def cloned?
60
+ File.directory? git_dir
61
+ end
62
+
63
+ private
64
+
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"
70
+ else
71
+ git "remote set-url cache #{@cache.path}", :path => @full_path
72
+ end
73
+ end
74
+
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
+ end
82
+
83
+ def fetch
84
+ set_cache_remote
85
+ git "fetch --prune cache", :path => @full_path
86
+ end
87
+
88
+ # Reset a git repo with a working directory to a specific ref
89
+ def reset
90
+ commit = cache.rev_parse(@ref)
91
+ current = rev_parse('HEAD')
92
+
93
+ if commit == current
94
+ logger.debug1 "Git repo #{@full_path} is already at #{commit}, no need to reset"
95
+ return
96
+ end
97
+
98
+ begin
99
+ git "reset --hard #{commit}", :path => @full_path
100
+ rescue R10K::ExecutionFailure => e
101
+ logger.error "Unable to locate commit object #{commit} in git repo #{@full_path}"
102
+ raise
103
+ end
104
+ end
105
+
106
+ # Resolve a ref to a commit hash
107
+ #
108
+ # @param [String] ref
109
+ #
110
+ # @return [String] The dereferenced hash of `ref`
111
+ def rev_parse(ref)
112
+ commit = git "rev-parse #{ref}^{commit}", :path => @full_path
113
+ commit.chomp
114
+ rescue R10K::ExecutionFailure => e
115
+ logger.error "Could not resolve ref #{ref.inspect} for git repo #{@full_path}"
116
+ raise
117
+ end
118
+
119
+ # @param [String] name The remote to retrieve the URl for
120
+ # @return [String] The git remote URL
121
+ def remote_url(remote_name)
122
+ output = git "remote -v", :path => @full_path
123
+
124
+ remotes = {}
125
+
126
+ output.each_line do |line|
127
+ if mdata = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)/)
128
+ name = mdata[1]
129
+ remote = mdata[2]
130
+ remotes[name] = remote
131
+ end
132
+ end
133
+
134
+ remotes[remote_name]
135
+ end
136
+
137
+ def git_dir
138
+ File.join(@full_path, '.git')
139
+ end
140
+ end
141
+ end
142
+ end
data/lib/r10k/logging.rb CHANGED
@@ -7,17 +7,21 @@ module R10K::Logging
7
7
 
8
8
  include Log4r
9
9
 
10
+ def logger_name
11
+ self.class.to_s
12
+ end
13
+
10
14
  def logger
11
15
  unless @logger
12
- @logger = Log4r::Logger.new(self.class.name)
16
+ @logger = Log4r::Logger.new(self.logger_name)
13
17
  @logger.add R10K::Logging.outputter
14
18
  end
15
19
  @logger
16
20
  end
17
21
 
18
22
  class << self
23
+ include Log4r
19
24
 
20
- include Log4r
21
25
  def included(klass)
22
26
  unless @log4r_loaded
23
27
  Configurator.custom_levels(*%w{DEBUG2 DEBUG1 DEBUG INFO NOTICE WARN ERROR FATAL})