anthill-librarian-puppet 3.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.md +337 -0
- data/bin/librarian-puppet +7 -0
- data/lib/librarian/puppet.rb +36 -0
- data/lib/librarian/puppet/action.rb +2 -0
- data/lib/librarian/puppet/action/install.rb +26 -0
- data/lib/librarian/puppet/action/resolve.rb +21 -0
- data/lib/librarian/puppet/cli.rb +110 -0
- data/lib/librarian/puppet/dependency.rb +18 -0
- data/lib/librarian/puppet/dsl.rb +92 -0
- data/lib/librarian/puppet/environment.rb +66 -0
- data/lib/librarian/puppet/extension.rb +9 -0
- data/lib/librarian/puppet/lockfile.rb +39 -0
- data/lib/librarian/puppet/source.rb +4 -0
- data/lib/librarian/puppet/source/forge.rb +181 -0
- data/lib/librarian/puppet/source/forge/repo.rb +158 -0
- data/lib/librarian/puppet/source/forge/repo_v1.rb +92 -0
- data/lib/librarian/puppet/source/forge/repo_v3.rb +62 -0
- data/lib/librarian/puppet/source/git.rb +74 -0
- data/lib/librarian/puppet/source/githubtarball.rb +130 -0
- data/lib/librarian/puppet/source/githubtarball/repo.rb +172 -0
- data/lib/librarian/puppet/source/local.rb +176 -0
- data/lib/librarian/puppet/source/path.rb +12 -0
- data/lib/librarian/puppet/source/repo.rb +38 -0
- data/lib/librarian/puppet/templates/Puppetfile +25 -0
- data/lib/librarian/puppet/util.rb +78 -0
- data/lib/librarian/puppet/version.rb +5 -0
- metadata +232 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/https'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require 'librarian/puppet/version'
|
7
|
+
require 'librarian/puppet/source/repo'
|
8
|
+
|
9
|
+
module Librarian
|
10
|
+
module Puppet
|
11
|
+
module Source
|
12
|
+
class GitHubTarball
|
13
|
+
class Repo < Librarian::Puppet::Source::Repo
|
14
|
+
include Librarian::Puppet::Util
|
15
|
+
|
16
|
+
TOKEN_KEY = 'GITHUB_API_TOKEN'
|
17
|
+
|
18
|
+
def versions
|
19
|
+
return @versions if @versions
|
20
|
+
data = api_call("/repos/#{source.uri}/tags")
|
21
|
+
if data.nil?
|
22
|
+
raise Error, "Unable to find module '#{source.uri}' on https://github.com"
|
23
|
+
end
|
24
|
+
|
25
|
+
all_versions = data.map { |r| r['name'].gsub(/^v/, '') }.sort.reverse
|
26
|
+
|
27
|
+
all_versions.delete_if do |version|
|
28
|
+
version !~ /\A\d+\.\d+(\.\d+.*)?\z/
|
29
|
+
end
|
30
|
+
|
31
|
+
@versions = all_versions.compact
|
32
|
+
debug { " Module #{name} found versions: #{@versions.join(", ")}" }
|
33
|
+
@versions
|
34
|
+
end
|
35
|
+
|
36
|
+
def manifests
|
37
|
+
versions.map do |version|
|
38
|
+
Manifest.new(source, name, version)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def install_version!(version, install_path)
|
43
|
+
if environment.local? && !vendored?(vendored_name, version)
|
44
|
+
raise Error, "Could not find a local copy of #{source.uri} at #{version}."
|
45
|
+
end
|
46
|
+
|
47
|
+
vendor_cache(source.uri.to_s, version) unless vendored?(vendored_name, version)
|
48
|
+
|
49
|
+
cache_version_unpacked! version
|
50
|
+
|
51
|
+
if install_path.exist? && rsync? != true
|
52
|
+
install_path.rmtree
|
53
|
+
end
|
54
|
+
|
55
|
+
unpacked_path = version_unpacked_cache_path(version).children.first
|
56
|
+
cp_r(unpacked_path, install_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def cache_version_unpacked!(version)
|
60
|
+
path = version_unpacked_cache_path(version)
|
61
|
+
return if path.directory?
|
62
|
+
|
63
|
+
path.mkpath
|
64
|
+
|
65
|
+
target = vendored?(vendored_name, version) ? vendored_path(vendored_name, version) : name
|
66
|
+
|
67
|
+
Librarian::Posix.run!(%W{tar xzf #{target} -C #{path}})
|
68
|
+
end
|
69
|
+
|
70
|
+
def vendor_cache(name, version)
|
71
|
+
clean_up_old_cached_versions(vendored_name(name))
|
72
|
+
|
73
|
+
url = "https://api.github.com/repos/#{name}/tarball/#{version}"
|
74
|
+
add_api_token_to_url(url)
|
75
|
+
|
76
|
+
environment.vendor!
|
77
|
+
File.open(vendored_path(vendored_name(name), version).to_s, 'wb') do |f|
|
78
|
+
begin
|
79
|
+
debug { "Downloading <#{url}> to <#{f.path}>" }
|
80
|
+
open(url,
|
81
|
+
"User-Agent" => "librarian-puppet v#{Librarian::Puppet::VERSION}") do |res|
|
82
|
+
while buffer = res.read(8192)
|
83
|
+
f.write(buffer)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
rescue OpenURI::HTTPError => e
|
87
|
+
raise e, "Error requesting <#{url}>: #{e.to_s}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def clean_up_old_cached_versions(name)
|
93
|
+
Dir["#{environment.vendor_cache}/#{name}*.tar.gz"].each do |old_version|
|
94
|
+
FileUtils.rm old_version
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def token_key_value
|
99
|
+
ENV[TOKEN_KEY]
|
100
|
+
end
|
101
|
+
|
102
|
+
def token_key_nil?
|
103
|
+
token_key_value.nil? || token_key_value.empty?
|
104
|
+
end
|
105
|
+
|
106
|
+
def add_api_token_to_url url
|
107
|
+
if token_key_nil?
|
108
|
+
debug { "#{TOKEN_KEY} environment value is empty or missing" }
|
109
|
+
elsif url.include? "?"
|
110
|
+
url << "&access_token=#{ENV[TOKEN_KEY]}"
|
111
|
+
else
|
112
|
+
url << "?access_token=#{ENV[TOKEN_KEY]}"
|
113
|
+
end
|
114
|
+
url
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def api_call(path)
|
120
|
+
tags = []
|
121
|
+
url = "https://api.github.com#{path}?page=1&per_page=100"
|
122
|
+
while true do
|
123
|
+
debug { " Module #{name} getting tags at: #{url}" }
|
124
|
+
add_api_token_to_url(url)
|
125
|
+
response = http_get(url, :headers => {
|
126
|
+
"User-Agent" => "librarian-puppet v#{Librarian::Puppet::VERSION}"
|
127
|
+
})
|
128
|
+
|
129
|
+
code, data = response.code.to_i, response.body
|
130
|
+
|
131
|
+
if code == 200
|
132
|
+
tags.concat JSON.parse(data)
|
133
|
+
else
|
134
|
+
begin
|
135
|
+
message = JSON.parse(data)['message']
|
136
|
+
if code == 403 && message && message.include?('API rate limit exceeded')
|
137
|
+
raise Error, message + " -- increase limit by authenticating via #{TOKEN_KEY}=your-token"
|
138
|
+
elsif message
|
139
|
+
raise Error, "Error fetching #{url}: [#{code}] #{message}"
|
140
|
+
end
|
141
|
+
rescue JSON::ParserError
|
142
|
+
# response does not return json
|
143
|
+
end
|
144
|
+
raise Error, "Error fetching #{url}: [#{code}] #{response.body}"
|
145
|
+
end
|
146
|
+
|
147
|
+
# next page
|
148
|
+
break if response["link"].nil?
|
149
|
+
next_link = response["link"].split(",").select{|l| l.match /rel=.*next.*/}
|
150
|
+
break if next_link.empty?
|
151
|
+
url = next_link.first.match(/<(.*)>/)[1]
|
152
|
+
end
|
153
|
+
return tags
|
154
|
+
end
|
155
|
+
|
156
|
+
def http_get(url, options)
|
157
|
+
uri = URI.parse(url)
|
158
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
159
|
+
http.use_ssl = true
|
160
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
161
|
+
options[:headers].each { |k, v| request.add_field k, v }
|
162
|
+
http.request(request)
|
163
|
+
end
|
164
|
+
|
165
|
+
def vendored_name(name = source.uri.to_s)
|
166
|
+
name.sub('/','-')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'librarian/puppet/util'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
module Puppet
|
5
|
+
module Source
|
6
|
+
module Local
|
7
|
+
include Librarian::Puppet::Util
|
8
|
+
|
9
|
+
def install!(manifest)
|
10
|
+
manifest.source == self or raise ArgumentError
|
11
|
+
|
12
|
+
debug { "Installing #{manifest}" }
|
13
|
+
|
14
|
+
name, version = manifest.name, manifest.version
|
15
|
+
found_path = found_path(name)
|
16
|
+
raise Error, "Path for #{name} doesn't contain a puppet module" if found_path.nil?
|
17
|
+
|
18
|
+
unless name.include? '/' or name.include? '-'
|
19
|
+
warn { "Invalid module name '#{name}', you should qualify it with 'ORGANIZATION-#{name}' for resolution to work correctly" }
|
20
|
+
end
|
21
|
+
|
22
|
+
install_path = environment.install_path.join(module_name(name))
|
23
|
+
if install_path.exist? && rsync? != true
|
24
|
+
debug { "Deleting #{relative_path_to(install_path)}" }
|
25
|
+
install_path.rmtree
|
26
|
+
end
|
27
|
+
|
28
|
+
install_perform_step_copy!(found_path, install_path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch_version(name, extra)
|
32
|
+
cache!
|
33
|
+
found_path = found_path(name)
|
34
|
+
module_version
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_dependencies(name, version, extra)
|
38
|
+
dependencies = Set.new
|
39
|
+
|
40
|
+
if specfile?
|
41
|
+
spec = environment.dsl(Pathname(specfile))
|
42
|
+
dependencies.merge spec.dependencies
|
43
|
+
end
|
44
|
+
|
45
|
+
parsed_metadata['dependencies'].each do |d|
|
46
|
+
gem_requirement = Librarian::Dependency::Requirement.new(d['version_requirement']).to_gem_requirement
|
47
|
+
new_dependency = Dependency.new(d['name'], gem_requirement, forge_source)
|
48
|
+
dependencies << new_dependency
|
49
|
+
end
|
50
|
+
|
51
|
+
dependencies
|
52
|
+
end
|
53
|
+
|
54
|
+
def forge_source
|
55
|
+
Forge.default
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Naming this method 'version' causes an exception to be raised.
|
61
|
+
def module_version
|
62
|
+
if parsed_metadata['version']
|
63
|
+
parsed_metadata['version']
|
64
|
+
else
|
65
|
+
warn { "Module #{to_s} does not have version, defaulting to 0.0.1" }
|
66
|
+
'0.0.1'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def require_puppet
|
71
|
+
begin
|
72
|
+
require 'puppet'
|
73
|
+
require 'puppet/module_tool'
|
74
|
+
rescue LoadError
|
75
|
+
$stderr.puts <<-EOF
|
76
|
+
Unable to load puppet, the puppet gem is required for :git and :path source.
|
77
|
+
Install it with: gem install puppet
|
78
|
+
EOF
|
79
|
+
exit 1
|
80
|
+
end
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def evaluate_modulefile(modulefile)
|
85
|
+
@@require_puppet ||= require_puppet
|
86
|
+
|
87
|
+
metadata = ::Puppet::ModuleTool::Metadata.new
|
88
|
+
|
89
|
+
# Puppet 4 does not have the class
|
90
|
+
unless defined? ::Puppet::ModuleTool::ModulefileReader
|
91
|
+
warn { "Can't parse Modulefile in Puppet >= 4.0 and you are using #{Librarian::Puppet::puppet_version}. Ignoring dependencies in #{modulefile}" }
|
92
|
+
return metadata
|
93
|
+
end
|
94
|
+
|
95
|
+
begin
|
96
|
+
::Puppet::ModuleTool::ModulefileReader.evaluate(metadata, modulefile)
|
97
|
+
raise SyntaxError, "Missing version" unless metadata.version
|
98
|
+
rescue ArgumentError, SyntaxError => error
|
99
|
+
warn { "Unable to parse #{modulefile}, ignoring: #{error}" }
|
100
|
+
if metadata.respond_to? :version=
|
101
|
+
metadata.version = '0.0.1' # puppet < 3.6
|
102
|
+
else
|
103
|
+
metadata.update({'version' => '0.0.1'}) # puppet >= 3.6
|
104
|
+
end
|
105
|
+
end
|
106
|
+
metadata
|
107
|
+
end
|
108
|
+
|
109
|
+
def parsed_metadata
|
110
|
+
if @metadata.nil?
|
111
|
+
@metadata = if metadata?
|
112
|
+
begin
|
113
|
+
JSON.parse(File.read(metadata))
|
114
|
+
rescue JSON::ParserError => e
|
115
|
+
raise Error, "Unable to parse json file #{metadata}: #{e}"
|
116
|
+
end
|
117
|
+
elsif modulefile?
|
118
|
+
# translate Modulefile to metadata.json
|
119
|
+
evaluated = evaluate_modulefile(modulefile)
|
120
|
+
{
|
121
|
+
'version' => evaluated.version,
|
122
|
+
'dependencies' => evaluated.dependencies.map do |dependency|
|
123
|
+
{
|
124
|
+
'name' => dependency.instance_variable_get(:@full_module_name),
|
125
|
+
'version_requirement' => dependency.instance_variable_get(:@version_requirement)
|
126
|
+
}
|
127
|
+
end
|
128
|
+
}
|
129
|
+
else
|
130
|
+
{}
|
131
|
+
end
|
132
|
+
@metadata['dependencies'] ||= []
|
133
|
+
end
|
134
|
+
@metadata
|
135
|
+
end
|
136
|
+
|
137
|
+
def modulefile
|
138
|
+
File.join(filesystem_path, 'Modulefile')
|
139
|
+
end
|
140
|
+
|
141
|
+
def modulefile?
|
142
|
+
File.exists?(modulefile)
|
143
|
+
end
|
144
|
+
|
145
|
+
def metadata
|
146
|
+
File.join(filesystem_path, 'metadata.json')
|
147
|
+
end
|
148
|
+
|
149
|
+
def metadata?
|
150
|
+
File.exists?(metadata)
|
151
|
+
end
|
152
|
+
|
153
|
+
def specfile
|
154
|
+
File.join(filesystem_path, environment.specfile_name)
|
155
|
+
end
|
156
|
+
|
157
|
+
def specfile?
|
158
|
+
File.exists?(specfile)
|
159
|
+
end
|
160
|
+
|
161
|
+
def install_perform_step_copy!(found_path, install_path)
|
162
|
+
debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
|
163
|
+
cp_r(found_path, install_path)
|
164
|
+
end
|
165
|
+
|
166
|
+
def manifest?(name, path)
|
167
|
+
return true if path.join('manifests').exist?
|
168
|
+
return true if path.join('lib').join('puppet').exist?
|
169
|
+
return true if path.join('lib').join('facter').exist?
|
170
|
+
debug { "Could not find manifests, lib/puppet or lib/facter under #{path}, maybe it is not a puppet module" }
|
171
|
+
true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# parent class for githubtarball and forge source Repos
|
2
|
+
module Librarian
|
3
|
+
module Puppet
|
4
|
+
module Source
|
5
|
+
class Repo
|
6
|
+
|
7
|
+
attr_accessor :source, :name
|
8
|
+
private :source=, :name=
|
9
|
+
|
10
|
+
def initialize(source, name)
|
11
|
+
self.source = source
|
12
|
+
self.name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def environment
|
16
|
+
source.environment
|
17
|
+
end
|
18
|
+
|
19
|
+
def cache_path
|
20
|
+
@cache_path ||= source.cache_path.join(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def version_unpacked_cache_path(version)
|
24
|
+
cache_path.join(version.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def vendored?(name, version)
|
28
|
+
vendored_path(name, version).exist?
|
29
|
+
end
|
30
|
+
|
31
|
+
def vendored_path(name, version)
|
32
|
+
environment.vendor_cache.join("#{name}-#{version}.tar.gz")
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#^syntax detection
|
3
|
+
|
4
|
+
forge "https://forgeapi.puppetlabs.com"
|
5
|
+
|
6
|
+
# use dependencies defined in metadata.json
|
7
|
+
metadata
|
8
|
+
|
9
|
+
# use dependencies defined in Modulefile
|
10
|
+
# modulefile
|
11
|
+
|
12
|
+
# A module from the Puppet Forge
|
13
|
+
# mod 'puppetlabs-stdlib'
|
14
|
+
|
15
|
+
# A module from git
|
16
|
+
# mod 'puppetlabs-ntp',
|
17
|
+
# :git => 'git://github.com/puppetlabs/puppetlabs-ntp.git'
|
18
|
+
|
19
|
+
# A module from a git branch/tag
|
20
|
+
# mod 'puppetlabs-apt',
|
21
|
+
# :git => 'https://github.com/puppetlabs/puppetlabs-apt.git',
|
22
|
+
# :ref => '1.4.x'
|
23
|
+
|
24
|
+
# A module from Github pre-packaged tarball
|
25
|
+
# mod 'puppetlabs-apache', '0.6.0', :github_tarball => 'puppetlabs/puppetlabs-apache'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rsync'
|
2
|
+
|
3
|
+
module Librarian
|
4
|
+
module Puppet
|
5
|
+
|
6
|
+
module Util
|
7
|
+
|
8
|
+
def debug(*args, &block)
|
9
|
+
environment.logger.debug(*args, &block)
|
10
|
+
end
|
11
|
+
def info(*args, &block)
|
12
|
+
environment.logger.info(*args, &block)
|
13
|
+
end
|
14
|
+
def warn(*args, &block)
|
15
|
+
environment.logger.warn(*args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rsync?
|
19
|
+
environment.config_db.local['rsync'] == 'true'
|
20
|
+
end
|
21
|
+
|
22
|
+
# workaround Issue #173 FileUtils.cp_r will fail if there is a symlink that points to a missing file
|
23
|
+
# or when the symlink is copied before the target file when preserve is true
|
24
|
+
# see also https://tickets.opscode.com/browse/CHEF-833
|
25
|
+
#
|
26
|
+
# If the rsync configuration parameter is set, use rsync instead of FileUtils
|
27
|
+
def cp_r(src, dest)
|
28
|
+
if rsync?
|
29
|
+
if Gem.win_platform?
|
30
|
+
src_clean = "#{src}".gsub(/^([a-z])\:/i,'/cygdrive/\1')
|
31
|
+
dest_clean = "#{dest}".gsub(/^([a-z])\:/i,'/cygdrive/\1')
|
32
|
+
else
|
33
|
+
src_clean = src
|
34
|
+
dest_clean = dest
|
35
|
+
end
|
36
|
+
debug { "Copying #{src_clean}/ to #{dest_clean}/ with rsync -avz --delete" }
|
37
|
+
result = Rsync.run(File.join(src_clean, "/"), File.join(dest_clean, "/"), ['-avz', '--delete'])
|
38
|
+
if result.success?
|
39
|
+
debug { "Rsync from #{src_clean}/ to #{dest_clean}/ successfull" }
|
40
|
+
else
|
41
|
+
msg = "Failed to rsync from #{src_clean}/ to #{dest_clean}/: " + result.error
|
42
|
+
raise Error, msg
|
43
|
+
end
|
44
|
+
else
|
45
|
+
begin
|
46
|
+
FileUtils.cp_r(src, dest, :preserve => true)
|
47
|
+
rescue Errno::ENOENT, Errno::EACCES
|
48
|
+
debug { "Failed to copy from #{src} to #{dest} preserving file types, trying again without preserving them" }
|
49
|
+
FileUtils.rm_rf(dest)
|
50
|
+
FileUtils.cp_r(src, dest)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Remove user and password from a URI object
|
56
|
+
def clean_uri(uri)
|
57
|
+
new_uri = uri.clone
|
58
|
+
new_uri.user = nil
|
59
|
+
new_uri.password = nil
|
60
|
+
new_uri
|
61
|
+
end
|
62
|
+
|
63
|
+
# normalize module name to use organization-module instead of organization/module
|
64
|
+
def normalize_name(name)
|
65
|
+
name.sub('/','-')
|
66
|
+
end
|
67
|
+
|
68
|
+
# get the module name from organization-module
|
69
|
+
def module_name(name)
|
70
|
+
# module name can't have dashes, so let's assume it is everything after the last dash
|
71
|
+
name.rpartition('-').last
|
72
|
+
end
|
73
|
+
|
74
|
+
# deprecated
|
75
|
+
alias :organization_name :module_name
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|