anthill-librarian-puppet 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|