anthill-librarian-puppet 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,12 @@
1
+ require 'librarian/source/path'
2
+ require 'librarian/puppet/source/local'
3
+
4
+ module Librarian
5
+ module Puppet
6
+ module Source
7
+ class Path < Librarian::Source::Path
8
+ include Local
9
+ end
10
+ end
11
+ end
12
+ 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