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.
- data/bin/r10k +1 -1
- data/lib/r10k.rb +0 -4
- data/lib/r10k/cli.rb +9 -5
- data/lib/r10k/cli/deploy.rb +108 -0
- data/lib/r10k/cli/environment.rb +5 -1
- data/lib/r10k/cli/environment/deploy.rb +6 -28
- data/lib/r10k/cli/environment/list.rb +6 -10
- data/lib/r10k/cli/environment/stale.rb +6 -16
- data/lib/r10k/cli/module.rb +5 -1
- data/lib/r10k/cli/module/deploy.rb +5 -32
- data/lib/r10k/cli/module/list.rb +6 -27
- data/lib/r10k/cli/puppetfile.rb +76 -0
- data/lib/r10k/cli/synchronize.rb +8 -24
- data/lib/r10k/cli/version.rb +22 -0
- data/lib/r10k/deployment.rb +55 -26
- data/lib/r10k/deployment/config.rb +69 -0
- data/lib/r10k/{config → deployment/config}/loader.rb +9 -5
- data/lib/r10k/deployment/environment.rb +88 -0
- data/lib/r10k/deployment/source.rb +79 -0
- data/lib/r10k/errors.rb +3 -5
- data/lib/r10k/execution.rb +43 -0
- data/lib/r10k/git/cache.rb +131 -0
- data/lib/r10k/git/errors.rb +34 -0
- data/lib/r10k/git/repository.rb +74 -0
- data/lib/r10k/git/working_dir.rb +142 -0
- data/lib/r10k/logging.rb +6 -2
- data/lib/r10k/module.rb +10 -13
- data/lib/r10k/module/forge.rb +35 -22
- data/lib/r10k/module/git.rb +18 -8
- data/lib/r10k/puppetfile.rb +107 -0
- data/lib/r10k/task.rb +13 -0
- data/lib/r10k/task/deployment.rb +151 -0
- data/lib/r10k/task/environment.rb +29 -0
- data/lib/r10k/task/module.rb +18 -0
- data/lib/r10k/task/puppetfile.rb +99 -0
- data/lib/r10k/task_runner.rb +72 -0
- data/lib/r10k/util/purgeable.rb +50 -0
- data/lib/r10k/version.rb +1 -1
- data/spec/unit/deployment/environment_spec.rb +19 -0
- data/spec/unit/git/cache_spec.rb +37 -0
- data/spec/unit/git/working_dir_spec.rb +15 -0
- data/spec/unit/module/forge_spec.rb +95 -0
- data/spec/unit/module_spec.rb +29 -0
- metadata +79 -44
- data/lib/r10k/action.rb +0 -7
- data/lib/r10k/action/environment.rb +0 -74
- data/lib/r10k/action/module.rb +0 -36
- data/lib/r10k/cli/cache.rb +0 -32
- data/lib/r10k/config.rb +0 -46
- data/lib/r10k/deployment/environment_collection.rb +0 -75
- data/lib/r10k/librarian.rb +0 -31
- data/lib/r10k/librarian/dsl.rb +0 -20
- data/lib/r10k/root.rb +0 -98
- 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.
|
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})
|