r10k 0.0.1rc1
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 +5 -0
- data/lib/r10k.rb +6 -0
- data/lib/r10k/cli.rb +33 -0
- data/lib/r10k/cli/environment.rb +26 -0
- data/lib/r10k/cli/environment/cache.rb +23 -0
- data/lib/r10k/cli/environment/deploy.rb +46 -0
- data/lib/r10k/cli/environment/list.rb +27 -0
- data/lib/r10k/cli/module.rb +25 -0
- data/lib/r10k/cli/module/deploy.rb +55 -0
- data/lib/r10k/cli/module/list.rb +45 -0
- data/lib/r10k/deployment.rb +60 -0
- data/lib/r10k/environment_collection.rb +53 -0
- data/lib/r10k/librarian.rb +31 -0
- data/lib/r10k/librarian/dsl.rb +20 -0
- data/lib/r10k/module.rb +45 -0
- data/lib/r10k/module/forge.rb +19 -0
- data/lib/r10k/module/git.rb +24 -0
- data/lib/r10k/root.rb +95 -0
- data/lib/r10k/synchro/git.rb +186 -0
- data/lib/r10k/util/interp.rb +25 -0
- data/lib/r10k/version.rb +3 -0
- metadata +117 -0
data/bin/r10k
ADDED
data/lib/r10k.rb
ADDED
data/lib/r10k/cli.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
def self.command
|
6
|
+
@cmd ||= Cri::Command.define do
|
7
|
+
name 'r10k'
|
8
|
+
usage 'r10k <subcommand> [options]'
|
9
|
+
summary 'Killer robot powered Puppet environment deployment'
|
10
|
+
description <<-EOD
|
11
|
+
r10k is a suite of commands to help deploy and manage puppet code for
|
12
|
+
complex environments.
|
13
|
+
EOD
|
14
|
+
|
15
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
16
|
+
puts cmd.help
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
required :c, :config, 'Specify a configuration file' do |value, cmd|
|
21
|
+
R10K::Deployment.instance.configfile = value
|
22
|
+
end
|
23
|
+
|
24
|
+
run do |opts, args, cmd|
|
25
|
+
puts cmd.help
|
26
|
+
exit 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'r10k/cli/environment'
|
33
|
+
require 'r10k/cli/module'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'r10k/cli'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
module Environment
|
6
|
+
def self.command
|
7
|
+
@cmd ||= Cri::Command.define do
|
8
|
+
name 'environment'
|
9
|
+
usage 'environment <subcommand>'
|
10
|
+
summary 'Operate on a specific environment'
|
11
|
+
|
12
|
+
required :e, :environment, 'Specify a particular environment'
|
13
|
+
|
14
|
+
run do |opts, args, cmd|
|
15
|
+
puts cmd.help
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self.command.add_command(Environment.command)
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'r10k/cli/environment/list'
|
25
|
+
require 'r10k/cli/environment/deploy'
|
26
|
+
require 'r10k/cli/environment/cache'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/synchro/git'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Environment
|
6
|
+
module Cache
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'cache'
|
10
|
+
usage 'cache'
|
11
|
+
summary 'Update cache for all sources'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
R10K::Deployment.instance[:sources].each_pair do |name, source|
|
15
|
+
synchro = R10K::Synchro::Git.new(source)
|
16
|
+
synchro.cache
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
self.command.add_command(Cache.command)
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module R10K::CLI::Environment
|
8
|
+
module Deploy
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'deploy'
|
12
|
+
usage 'deploy'
|
13
|
+
summary 'Deploy an environment'
|
14
|
+
|
15
|
+
flag :r, :recurse, 'Recursively update submodules'
|
16
|
+
|
17
|
+
required :u, :update, "Enable or disable cache updating"
|
18
|
+
|
19
|
+
run do |opts, args, cmd|
|
20
|
+
deployment = R10K::Deployment.instance
|
21
|
+
env_list = deployment.environments
|
22
|
+
|
23
|
+
update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
|
24
|
+
|
25
|
+
if opts[:environment]
|
26
|
+
environments = env_list.select {|env| env.name == opts[:environment]}
|
27
|
+
else
|
28
|
+
environments = env_list
|
29
|
+
end
|
30
|
+
|
31
|
+
environments.each do |env|
|
32
|
+
FileUtils.mkdir_p env.full_path
|
33
|
+
env.sync! :update_cache => update_cache
|
34
|
+
|
35
|
+
if opts[:recurse]
|
36
|
+
env.modules.each do |mod|
|
37
|
+
mod.sync! :update_cache => update_cache
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self.command.add_command(Deploy.command)
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'r10k/cli/environment'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Environment
|
6
|
+
module List
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'list'
|
10
|
+
usage 'list'
|
11
|
+
summary 'List all available environments'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
deployment = R10K::Deployment.instance
|
15
|
+
output = deployment.environments.inject('') do |str, root|
|
16
|
+
str << " - "
|
17
|
+
str << "#{root.name}: #{root.full_path}"
|
18
|
+
str << "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
puts output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
self.command.add_command(List.command)
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'r10k/cli'
|
2
|
+
require 'cri'
|
3
|
+
|
4
|
+
module R10K::CLI
|
5
|
+
module Module
|
6
|
+
def self.command
|
7
|
+
@cmd ||= Cri::Command.define do
|
8
|
+
name 'module'
|
9
|
+
usage 'module <subcommand>'
|
10
|
+
summary 'Operate on a specific puppet module'
|
11
|
+
|
12
|
+
required :e, :environment, 'Specify a particular environment'
|
13
|
+
|
14
|
+
run do |opts, args, cmd|
|
15
|
+
puts cmd.help
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self.command.add_command(Module.command)
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'r10k/cli/module/deploy'
|
25
|
+
require 'r10k/cli/module/list'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'r10k/cli/module'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module R10K::CLI::Module
|
8
|
+
module Deploy
|
9
|
+
def self.command
|
10
|
+
@cmd ||= Cri::Command.define do
|
11
|
+
name 'deploy'
|
12
|
+
usage 'deploy <module name>'
|
13
|
+
summary 'Deploy a module'
|
14
|
+
|
15
|
+
required :u, :update, "Enable or disable cache updating"
|
16
|
+
|
17
|
+
run do |opts, args, cmd|
|
18
|
+
|
19
|
+
unless (module_name = args[0])
|
20
|
+
puts cmd.help
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
deployment = R10K::Deployment.instance
|
25
|
+
env_list = deployment.environments
|
26
|
+
|
27
|
+
update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
|
28
|
+
|
29
|
+
if opts[:environment]
|
30
|
+
environments = env_list.select {|env| env.name == opts[:environment]}
|
31
|
+
else
|
32
|
+
environments = env_list
|
33
|
+
end
|
34
|
+
|
35
|
+
environments.each do |env|
|
36
|
+
FileUtils.mkdir_p env.full_path
|
37
|
+
env.sync! :update_cache => update_cache
|
38
|
+
|
39
|
+
mods = env.modules.select { |mod| mod.name == module_name }
|
40
|
+
|
41
|
+
if mods.empty?
|
42
|
+
puts "No modules with name #{module_name} matched in environments #{env.map(&:name).inspect}".red
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
|
46
|
+
mods.each do |mod|
|
47
|
+
mod.sync! :update_cache => update_cache
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
self.command.add_command(Deploy.command)
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'r10k/cli/module'
|
2
|
+
require 'r10k/deployment'
|
3
|
+
require 'cri'
|
4
|
+
|
5
|
+
module R10K::CLI::Module
|
6
|
+
module List
|
7
|
+
def self.command
|
8
|
+
@cmd ||= Cri::Command.define do
|
9
|
+
name 'list'
|
10
|
+
usage 'list'
|
11
|
+
summary 'List modules that are instantiated in environments'
|
12
|
+
|
13
|
+
run do |opts, args, cmd|
|
14
|
+
deployment = R10K::Deployment.instance
|
15
|
+
env_list = deployment.environments
|
16
|
+
|
17
|
+
update_cache = (defined? opts[:update]) ? (opts[:update] == 'true') : false
|
18
|
+
|
19
|
+
if opts[:environment]
|
20
|
+
environments = env_list.select {|env| env.name == opts[:environment]}
|
21
|
+
else
|
22
|
+
environments = env_list
|
23
|
+
end
|
24
|
+
|
25
|
+
printree = {}
|
26
|
+
|
27
|
+
environments.each do |env|
|
28
|
+
module_names = env.modules.map(&:name)
|
29
|
+
|
30
|
+
printree[env.name] = module_names
|
31
|
+
end
|
32
|
+
|
33
|
+
printree.each_pair do |env_name, mod_list|
|
34
|
+
puts " - #{env_name}"
|
35
|
+
mod_list.each do |mod|
|
36
|
+
puts " #{mod}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self.command.add_command(List.command)
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/synchro/git'
|
3
|
+
require 'r10k/environment_collection'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
class R10K::Deployment
|
7
|
+
# Model a full installation of module directories and modules.
|
8
|
+
|
9
|
+
def self.instance
|
10
|
+
@myself ||= self.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@configfile = File.join(Dir.getwd, "config.yaml")
|
15
|
+
@update_cache = true
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :configfile
|
19
|
+
|
20
|
+
# Load up all module roots
|
21
|
+
#
|
22
|
+
# @return [Array<R10K::Root>]
|
23
|
+
def environments
|
24
|
+
collection = R10K::EnvironmentCollection.new(config)
|
25
|
+
collection.to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
# Serve up the loaded config if it's already been loaded, otherwise try to
|
29
|
+
# load a config in the current wd.
|
30
|
+
def config
|
31
|
+
load_config unless @config
|
32
|
+
@config
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Object] A top level key from the config hash
|
36
|
+
def setting(key)
|
37
|
+
self.config[key]
|
38
|
+
end
|
39
|
+
alias_method :[], :setting
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Load and store a config file, and set relevant options
|
44
|
+
#
|
45
|
+
# @param [String] configfile The path to the YAML config file
|
46
|
+
def load_config
|
47
|
+
File.open(@configfile) { |fh| @config = YAML.load(fh.read) }
|
48
|
+
apply_config_settings
|
49
|
+
@config
|
50
|
+
rescue => e
|
51
|
+
raise "Couldn't load #{configfile}: #{e}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Apply config settings to the relevant classes after a config has been loaded.
|
55
|
+
def apply_config_settings
|
56
|
+
if @config[:cachedir]
|
57
|
+
R10K::Synchro::Git.cache_root = @config[:cachedir]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
|
3
|
+
class R10K::EnvironmentCollection
|
4
|
+
|
5
|
+
attr_reader :update_cache
|
6
|
+
|
7
|
+
def initialize(config, options = {:update_cache => true})
|
8
|
+
@config = config
|
9
|
+
@environments = []
|
10
|
+
|
11
|
+
@update_cache = options.delete(:update_cache)
|
12
|
+
|
13
|
+
load_all
|
14
|
+
end
|
15
|
+
|
16
|
+
# List subdirectories that aren't associated with an env
|
17
|
+
#
|
18
|
+
# If a branch associated with an environment is deleted then the associated
|
19
|
+
# branch ceases to be tracked. This method will scan a directory for
|
20
|
+
# subdirectories and return any subdirectories that don't have an active
|
21
|
+
# branch associated.
|
22
|
+
#
|
23
|
+
# @param [String] basedir The directory to scan
|
24
|
+
#
|
25
|
+
# @return [Array<String>] A list of filenames
|
26
|
+
def untracked_environments(basedir)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Array<R10K::Root>]
|
31
|
+
def to_a
|
32
|
+
@environments
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def load_all
|
38
|
+
@config[:sources].each_pair do |repo_name, repo_config|
|
39
|
+
synchro = R10K::Synchro::Git.new(repo_config['remote'])
|
40
|
+
synchro.cache if @update_cache
|
41
|
+
|
42
|
+
if repo_config['ref']
|
43
|
+
@environments << R10K::Root.new(repo_config)
|
44
|
+
else
|
45
|
+
synchro.branches.each do |branch|
|
46
|
+
@environments << R10K::Root.new(repo_config.merge({'ref' => branch}))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@environments
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
|
4
|
+
class R10K::Librarian
|
5
|
+
|
6
|
+
attr_reader :forge
|
7
|
+
|
8
|
+
def initialize(puppetfile)
|
9
|
+
@puppetfile = puppetfile
|
10
|
+
@modules = []
|
11
|
+
@forge = 'forge.puppetlabs.com'
|
12
|
+
end
|
13
|
+
|
14
|
+
def load
|
15
|
+
dsl = R10K::Librarian::DSL.new(self)
|
16
|
+
dsl.instance_eval(File.read(@puppetfile), @puppetfile)
|
17
|
+
|
18
|
+
@modules
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method only exists because people tried being excessively clever.
|
22
|
+
def set_forge(forge)
|
23
|
+
@forge = forge
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_module(name, args)
|
27
|
+
@modules << [name, args]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'r10k/librarian/dsl'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'r10k/librarian'
|
2
|
+
|
3
|
+
class R10K::Librarian::DSL
|
4
|
+
|
5
|
+
def initialize(librarian)
|
6
|
+
@librarian = librarian
|
7
|
+
end
|
8
|
+
|
9
|
+
def mod(name, args = [])
|
10
|
+
@librarian.add_module(name, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def forge(location)
|
14
|
+
@librarian.set_forge(location)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
raise NoMethodError, "unrecognized declaration '#{method}'"
|
19
|
+
end
|
20
|
+
end
|
data/lib/r10k/module.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
|
3
|
+
class R10K::Module
|
4
|
+
|
5
|
+
# Register an inheriting class for later generation
|
6
|
+
def self.inherited(klass)
|
7
|
+
@klasses ||= []
|
8
|
+
@klasses << klass
|
9
|
+
end
|
10
|
+
|
11
|
+
# Look up the implementing class and instantiate an object
|
12
|
+
#
|
13
|
+
# This method takes the arguments for normal object generation and checks all
|
14
|
+
# inheriting classes to see if they implement the behavior needed to create
|
15
|
+
# the requested object. It selects the first class that can implement an object
|
16
|
+
# with `name, args`, and generates an object of that class.
|
17
|
+
#
|
18
|
+
# @param [String] name The unique name of the module
|
19
|
+
# @param [String] path The root path to install the module in
|
20
|
+
# @param [Object] args An arbitary value or set of values that specifies the implementation
|
21
|
+
#
|
22
|
+
# @return [Object < R10K::Module] A member of the implementing subclass
|
23
|
+
def self.new(name, path, args)
|
24
|
+
if implementation = @klasses.find { |klass| klass.implements(name, args) }
|
25
|
+
obj = implementation.send(:allocate)
|
26
|
+
obj.send(:initialize, name, path, args)
|
27
|
+
obj
|
28
|
+
else
|
29
|
+
raise "Module #{name} with args #{args.inspect} doesn't have an implementation. (Are you using the right arguments?)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :name, :path
|
34
|
+
|
35
|
+
def initialize(name, path, args)
|
36
|
+
@name, @path, @args = name, path, args
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_path
|
40
|
+
File.join(@path, @name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'r10k/module/git'
|
45
|
+
require 'r10k/module/forge'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
|
4
|
+
class R10K::Module::Forge < R10K::Module
|
5
|
+
|
6
|
+
def self.implements(name, args)
|
7
|
+
args.is_a? String and args.match /\d+\.\d+\.\d+/
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(name, path, args)
|
11
|
+
super
|
12
|
+
|
13
|
+
@version = @args
|
14
|
+
end
|
15
|
+
|
16
|
+
def sync!(options = {})
|
17
|
+
puts "#{self.class.name}#sync! is not implemented. Doing nothing."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
require 'r10k/synchro/git'
|
4
|
+
|
5
|
+
class R10K::Module::Git < R10K::Module
|
6
|
+
|
7
|
+
def self.implements(name, args)
|
8
|
+
args.is_a? Hash and args.has_key?(:git)
|
9
|
+
rescue
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(name, path, args)
|
14
|
+
super
|
15
|
+
|
16
|
+
@remote = @args[:git]
|
17
|
+
@ref = (@args[:ref] || 'master')
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync!(options = {})
|
21
|
+
synchro = R10K::Synchro::Git.new(@remote)
|
22
|
+
synchro.sync(full_path, @ref, options)
|
23
|
+
end
|
24
|
+
end
|
data/lib/r10k/root.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'r10k/module'
|
3
|
+
require 'r10k/synchro/git'
|
4
|
+
|
5
|
+
class R10K::Root
|
6
|
+
|
7
|
+
# @!attribute [r] name
|
8
|
+
# The directory name of this root
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
# @!attribute [r] basedir
|
12
|
+
# The basedir to clone the root into
|
13
|
+
attr_reader :basedir
|
14
|
+
|
15
|
+
# @!attribute [r] remote
|
16
|
+
# The location of the remote git repository
|
17
|
+
attr_reader :remote
|
18
|
+
|
19
|
+
# @!attribute [r] ref
|
20
|
+
# The git ref to instantiate into the basedir
|
21
|
+
attr_reader :ref
|
22
|
+
|
23
|
+
def initialize(hash)
|
24
|
+
parse_initialize_hash(hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def sync!(options = {})
|
28
|
+
synchro = R10K::Synchro::Git.new(@remote)
|
29
|
+
recursive_needed = !(synchro.cloned?(full_path))
|
30
|
+
synchro.sync(full_path, @ref, options)
|
31
|
+
|
32
|
+
sync_modules!(options) if recursive_needed
|
33
|
+
end
|
34
|
+
|
35
|
+
def sync_modules!(options = {})
|
36
|
+
modules.each do |mod|
|
37
|
+
mod.sync!(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def modules
|
42
|
+
librarian = R10K::Librarian.new("#{full_path}/Puppetfile")
|
43
|
+
|
44
|
+
module_data = librarian.load
|
45
|
+
|
46
|
+
@modules = module_data.map do |mod|
|
47
|
+
name = mod[0]
|
48
|
+
args = mod[1]
|
49
|
+
R10K::Module.new(name, "#{full_path}/modules", args)
|
50
|
+
end
|
51
|
+
rescue Errno::ENOENT
|
52
|
+
puts "#{self}: #{full_path} does not exist, cannot enumerate modules."
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
|
56
|
+
def full_path
|
57
|
+
@full_path ||= File.expand_path(File.join @basedir, @name)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def parse_initialize_hash(hash)
|
63
|
+
if hash['name']
|
64
|
+
@name = hash.delete('name')
|
65
|
+
elsif hash['ref']
|
66
|
+
@name = hash['ref']
|
67
|
+
else
|
68
|
+
raise "Unable to resolve directory name from options #{hash.inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
# XXX This could be metaprogrammed, but it seems like the road to madness.
|
72
|
+
|
73
|
+
if hash['basedir']
|
74
|
+
@basedir = hash.delete('basedir')
|
75
|
+
else
|
76
|
+
raise "'basedir' is a required value for #{self.class}.new"
|
77
|
+
end
|
78
|
+
|
79
|
+
if hash['remote']
|
80
|
+
@remote = hash.delete('remote')
|
81
|
+
else
|
82
|
+
raise "'remote' is a required value for #{self.class}.new"
|
83
|
+
end
|
84
|
+
|
85
|
+
if hash['ref']
|
86
|
+
@ref = hash.delete('ref')
|
87
|
+
else
|
88
|
+
raise "'ref' is a required value for #{self.class}.new"
|
89
|
+
end
|
90
|
+
|
91
|
+
unless hash.empty?
|
92
|
+
raise "#{self.class}.new only expects keys ['name', 'basedir', 'remote', 'ref'], got #{hash.keys.inspect}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'r10k'
|
2
|
+
require 'shellter'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module R10K::Synchro; end
|
6
|
+
|
7
|
+
class R10K::Synchro::Git
|
8
|
+
# Define a thingy that can synchronize git repositories.
|
9
|
+
#
|
10
|
+
# This class is built to be a general purpose mechanism for syncing and
|
11
|
+
# caching git repositories.
|
12
|
+
#
|
13
|
+
# Class instances are memoized based on the git remote path. This way if a
|
14
|
+
# single git repository is instantiated multiple times, the object cache
|
15
|
+
# will only be updated once.
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :cache_root
|
19
|
+
|
20
|
+
# @return [Hash<R10K::Synchro::Git>] A hash of memoized class instances
|
21
|
+
def synchros
|
22
|
+
@synchros ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Memoize class instances and return existing instances.
|
26
|
+
#
|
27
|
+
# This allows objects to mark themselves as cached to prevent unnecessary
|
28
|
+
# cache refreshes.
|
29
|
+
#
|
30
|
+
# @param [String] remote A git remote URL
|
31
|
+
# @return [R10K::Synchro::Git]
|
32
|
+
def new(remote)
|
33
|
+
unless synchros[remote]
|
34
|
+
obj = self.allocate
|
35
|
+
obj.send(:initialize, remote)
|
36
|
+
synchros[remote] = obj
|
37
|
+
end
|
38
|
+
synchros[remote]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :remote
|
43
|
+
|
44
|
+
# Instantiates a new git synchro and optionally prepares for caching
|
45
|
+
#
|
46
|
+
# @param [String] remote A git remote URL
|
47
|
+
def initialize(remote)
|
48
|
+
@remote = remote
|
49
|
+
|
50
|
+
if self.class.cache_root
|
51
|
+
@cache_path = File.join(self.class.cache_root, @remote.gsub(/[^@\w-]/, '-'))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Synchronize the local git repository.
|
56
|
+
#
|
57
|
+
# @param [String] path The destination path for the files
|
58
|
+
# @param [String] ref The git ref to instantiate at the destination path
|
59
|
+
def sync(path, ref, options = {:update_cache => true})
|
60
|
+
path = File.expand_path(path)
|
61
|
+
cache if options[:update_cache]
|
62
|
+
|
63
|
+
if self.cloned?(path)
|
64
|
+
fetch(path)
|
65
|
+
else
|
66
|
+
clone(path)
|
67
|
+
end
|
68
|
+
reset(path, ref)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [TrueClass] if the git repository is cached
|
72
|
+
def has_cache?
|
73
|
+
@cache_path and File.directory? @cache_path
|
74
|
+
end
|
75
|
+
|
76
|
+
# Determine if repo has been cloned into a specific dir
|
77
|
+
#
|
78
|
+
# @param [String] dirname The directory to check
|
79
|
+
#
|
80
|
+
# @return [true, false] If the repo has already been cloned
|
81
|
+
def cloned?(directory)
|
82
|
+
File.directory?(File.join(directory, '.git'))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Update the git object cache repository if it hasn't been done
|
86
|
+
#
|
87
|
+
# @return [true, nil] If the cache was actually updated
|
88
|
+
def cache
|
89
|
+
unless @cached
|
90
|
+
cache!
|
91
|
+
@cached = true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Force a cache refresh
|
96
|
+
def cache!
|
97
|
+
if has_cache?
|
98
|
+
git "--git-dir #{@cache_path} fetch --prune"
|
99
|
+
else
|
100
|
+
FileUtils.mkdir_p File.dirname(File.join(@cache_path))
|
101
|
+
git "clone --mirror #{@remote} #{@cache_path}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Retrieve a list of cached branches for the git repo associated with this
|
106
|
+
# object.
|
107
|
+
#
|
108
|
+
# @return [Array<String>] A list of all cached remote branches
|
109
|
+
def branches
|
110
|
+
cache
|
111
|
+
output = git "--git-dir #{@cache_path} branch"
|
112
|
+
output.split("\n").map { |str| str[2..-1] }
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Perform a non-bare clone of a git repository.
|
118
|
+
#
|
119
|
+
# If a cachedir is available and the repo is already cached, it will be
|
120
|
+
# used as an object reference to speed up the clone.
|
121
|
+
#
|
122
|
+
# @param [String] path The directory to create the repo working directory
|
123
|
+
def clone(path)
|
124
|
+
if has_cache?
|
125
|
+
git "clone --reference #{@cache_path} #{@remote} #{path}"
|
126
|
+
else
|
127
|
+
FileUtils.mkdir_p path unless File.directory? path
|
128
|
+
git "clone #{@remote} #{path}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def fetch(path)
|
133
|
+
if has_cache?
|
134
|
+
git "fetch --prune #{@cache_path}", path
|
135
|
+
else
|
136
|
+
git "fetch --prune", path
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Reset a git repo with a working directory to a specific ref
|
141
|
+
#
|
142
|
+
# @param [String] path The path to the working directory of the git repo
|
143
|
+
# @param [String] ref The git reference to reset to.
|
144
|
+
def reset(path, ref)
|
145
|
+
|
146
|
+
# Helloooo, hackery. Try to parse the ref as a commit object. If that fails
|
147
|
+
# this probably means that the ref is a remote branch. For the sake of
|
148
|
+
# brevity this code blindly makes that assumption on the failure of
|
149
|
+
# `git rev-parse`.
|
150
|
+
begin
|
151
|
+
commit = git "--git-dir #{@cache_path} rev-parse #{ref}^{commit}"
|
152
|
+
rescue RuntimeError => e
|
153
|
+
commit = "origin/#{ref}"
|
154
|
+
end
|
155
|
+
|
156
|
+
git "reset --hard #{commit}", path
|
157
|
+
end
|
158
|
+
|
159
|
+
# Wrap git commands
|
160
|
+
#
|
161
|
+
# @param [String] command_line_args The arguments for the git prompt
|
162
|
+
# @param [String] git_dir An optional git working directory
|
163
|
+
#
|
164
|
+
# @return [String] The git command output
|
165
|
+
def git(command_line_args, git_dir = nil)
|
166
|
+
args = []
|
167
|
+
|
168
|
+
if git_dir
|
169
|
+
args << "--work-tree" << git_dir
|
170
|
+
args << "--git-dir" << "#{git_dir}/.git"
|
171
|
+
end
|
172
|
+
|
173
|
+
args << command_line_args.split(/\s+/)
|
174
|
+
|
175
|
+
result = Shellter.run!('git', args.join(' '))
|
176
|
+
puts "Execute: #{result.last_command}".green
|
177
|
+
|
178
|
+
stderr = result.stderr.read
|
179
|
+
stdout = result.stdout.read
|
180
|
+
|
181
|
+
puts stdout.blue unless stdout.empty?
|
182
|
+
puts stderr.red unless stderr.empty?
|
183
|
+
|
184
|
+
stdout
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module R10K; end
|
3
|
+
module R10K::Util; end
|
4
|
+
|
5
|
+
module R10K::Util::Interp
|
6
|
+
|
7
|
+
# Interpolate a string with a given scope
|
8
|
+
#
|
9
|
+
# @param [String] string
|
10
|
+
# @param [Hash] scope
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
def interpolate_string(string, scope)
|
14
|
+
|
15
|
+
interp = string.clone
|
16
|
+
|
17
|
+
while (matchdata = interp.match /%\{.+?\}/)
|
18
|
+
var_name = matchdata[1].intern
|
19
|
+
var_data = scope[var_name]
|
20
|
+
interp.gsub!(/%\{#{var_name}\}/, var_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
interp
|
24
|
+
end
|
25
|
+
end
|
data/lib/r10k/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: r10k
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1rc1
|
5
|
+
prerelease: 5
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- adrien@somethingsinistral.net
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.3.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.3.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: shellter
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.6
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.9.6
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: popen4
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.1.2
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.2
|
62
|
+
description: ! " R10K is an implementation of the Dynamic Puppet environments based
|
63
|
+
on git repositories\n as described in http://puppetlabs.com/blog/git-workflow-and-puppet-environments/.
|
64
|
+
It\n aggressively caches and tries to minimize network activity to ensure that
|
65
|
+
interactive\n deployment is as fast as possible.\n"
|
66
|
+
email:
|
67
|
+
executables:
|
68
|
+
- r10k
|
69
|
+
extensions: []
|
70
|
+
extra_rdoc_files: []
|
71
|
+
files:
|
72
|
+
- bin/r10k
|
73
|
+
- lib/r10k/cli/environment/cache.rb
|
74
|
+
- lib/r10k/cli/environment/deploy.rb
|
75
|
+
- lib/r10k/cli/environment/list.rb
|
76
|
+
- lib/r10k/cli/environment.rb
|
77
|
+
- lib/r10k/cli/module/deploy.rb
|
78
|
+
- lib/r10k/cli/module/list.rb
|
79
|
+
- lib/r10k/cli/module.rb
|
80
|
+
- lib/r10k/cli.rb
|
81
|
+
- lib/r10k/deployment.rb
|
82
|
+
- lib/r10k/environment_collection.rb
|
83
|
+
- lib/r10k/librarian/dsl.rb
|
84
|
+
- lib/r10k/librarian.rb
|
85
|
+
- lib/r10k/module/forge.rb
|
86
|
+
- lib/r10k/module/git.rb
|
87
|
+
- lib/r10k/module.rb
|
88
|
+
- lib/r10k/root.rb
|
89
|
+
- lib/r10k/synchro/git.rb
|
90
|
+
- lib/r10k/util/interp.rb
|
91
|
+
- lib/r10k/version.rb
|
92
|
+
- lib/r10k.rb
|
93
|
+
homepage: http://github.com/adrienthebo/r10k
|
94
|
+
licenses: []
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>'
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.1
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 1.8.23
|
114
|
+
signing_key:
|
115
|
+
specification_version: 3
|
116
|
+
summary: Dynamic Puppet environments with Git
|
117
|
+
test_files: []
|