librarian-puppet-rethinc 3.0.1.1

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.
@@ -0,0 +1,123 @@
1
+ require 'librarian/dsl'
2
+ require 'librarian/dsl/target'
3
+ require 'librarian/puppet/source'
4
+ require 'librarian/puppet/dependency'
5
+
6
+ module Librarian
7
+ module Puppet
8
+ class Dsl < Librarian::Dsl
9
+
10
+ FORGE_URL = "https://forgeapi.puppetlabs.com"
11
+
12
+ dependency :mod
13
+
14
+ source :forge => Source::Forge
15
+ source :git => Source::Git
16
+ source :path => Source::Path
17
+ source :github_tarball => Source::GitHubTarball
18
+
19
+ def default_specfile
20
+ Proc.new do
21
+ forge FORGE_URL
22
+ metadata
23
+ end
24
+ end
25
+
26
+ def self.dependency_type
27
+ Librarian::Puppet::Dependency
28
+ end
29
+
30
+ def post_process_target(target)
31
+ # save the default forge defined
32
+ default_forge = target.sources.select {|s| s.is_a? Librarian::Puppet::Source::Forge}.first
33
+ Librarian::Puppet::Source::Forge.default = default_forge || Librarian::Puppet::Source::Forge.from_lock_options(environment, :remote => FORGE_URL)
34
+ end
35
+
36
+ def receiver(target)
37
+ Receiver.new(target)
38
+ end
39
+
40
+ def run(specfile = nil, sources = [])
41
+ specfile, sources = nil, specfile if specfile.kind_of?(Array) && sources.empty?
42
+
43
+ Target.new(self).tap do |target|
44
+ target.precache_sources(sources)
45
+ debug_named_source_cache("Pre-Cached Sources", target)
46
+
47
+ specfile ||= Proc.new if block_given?
48
+
49
+ if specfile.kind_of?(Pathname) and !File.exists?(specfile)
50
+ debug { "Specfile #{specfile} not found, using defaults" } unless specfile.nil?
51
+ receiver(target).run(specfile, &default_specfile)
52
+ else
53
+ receiver(target).run(specfile)
54
+ end
55
+
56
+ post_process_target(target)
57
+
58
+ debug_named_source_cache("Post-Cached Sources", target)
59
+ end.to_spec
60
+ end
61
+
62
+ class Target < Librarian::Dsl::Target
63
+ def dependency(name, *args)
64
+ options = args.last.is_a?(Hash) ? args.pop : {}
65
+ source = source_from_options(options) || @source
66
+ dep = dependency_type.new(name, args, source, 'Puppetfile')
67
+ @dependencies << dep
68
+ end
69
+ end
70
+
71
+ class Receiver < Librarian::Dsl::Receiver
72
+ attr_reader :specfile, :working_path
73
+
74
+ # save the specfile and call librarian
75
+ def run(specfile = nil)
76
+ @working_path = specfile.kind_of?(Pathname) ? specfile.parent : Pathname.new(Dir.pwd)
77
+ @specfile = specfile
78
+ super
79
+ end
80
+
81
+ # implement the 'modulefile' syntax for Puppetfile
82
+ def modulefile
83
+ f = modulefile_path
84
+ raise Error, "Modulefile file does not exist: #{f}" unless File.exists?(f)
85
+ File.read(f).lines.each do |line|
86
+ regexp = /\s*dependency\s+('|")([^'"]+)\1\s*(?:,\s*('|")([^'"]+)\3)?/
87
+ regexp =~ line && mod($2, $4)
88
+ end
89
+ end
90
+
91
+ # implement the 'metadata' syntax for Puppetfile
92
+ def metadata
93
+ f = working_path.join('metadata.json')
94
+ unless File.exists?(f)
95
+ msg = "Metadata file does not exist: #{f}"
96
+ # try modulefile, in case we don't have a Puppetfile and we are using the default template
97
+ if File.exists?(modulefile_path)
98
+ modulefile
99
+ return
100
+ else
101
+ raise Error, msg
102
+ end
103
+ end
104
+ begin
105
+ json = JSON.parse(File.read(f))
106
+ rescue JSON::ParserError => e
107
+ raise Error, "Unable to parse json file #{f}: #{e}"
108
+ end
109
+ dependencyList = json['dependencies']
110
+ dependencyList.each do |d|
111
+ mod(d['name'], d['version_requirement'])
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def modulefile_path
118
+ working_path.join('Modulefile')
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,66 @@
1
+ require "librarian/environment"
2
+ require "librarian/puppet/dsl"
3
+ require "librarian/puppet/source"
4
+ require "librarian/puppet/lockfile"
5
+
6
+ module Librarian
7
+ module Puppet
8
+ class Environment < Librarian::Environment
9
+
10
+ def adapter_name
11
+ "puppet"
12
+ end
13
+
14
+ def lockfile
15
+ Lockfile.new(self, lockfile_path)
16
+ end
17
+
18
+ def ephemeral_lockfile
19
+ Lockfile.new(self, nil)
20
+ end
21
+
22
+ def tmp_path
23
+ part = config_db["tmp"] || ".tmp"
24
+ project_path.join(part)
25
+ end
26
+
27
+ def install_path
28
+ part = config_db["path"] || "modules"
29
+ project_path.join(part)
30
+ end
31
+
32
+ def vendor_path
33
+ project_path.join('vendor/puppet')
34
+ end
35
+
36
+ def vendor_cache
37
+ vendor_path.join('cache')
38
+ end
39
+
40
+ def vendor_source
41
+ vendor_path.join('source')
42
+ end
43
+
44
+ def vendor!
45
+ vendor_cache.mkpath unless vendor_cache.exist?
46
+ vendor_source.mkpath unless vendor_source.exist?
47
+ end
48
+
49
+ def vendor?
50
+ vendor_path.exist?
51
+ end
52
+
53
+ def local?
54
+ config_db['mode'] == 'local'
55
+ end
56
+
57
+ def use_v1_api
58
+ config_db['use-v1-api']
59
+ end
60
+
61
+ def use_short_cache_path
62
+ config_db['use-short-cache-path']
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,9 @@
1
+ require 'librarian/puppet/environment'
2
+ require 'librarian/action/base'
3
+
4
+ module Librarian
5
+ module Puppet
6
+ extend self
7
+ extend Librarian
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ # Extend Lockfile to normalize module names from acme/mod to acme-mod
2
+ module Librarian
3
+ module Puppet
4
+ class Lockfile < Librarian::Lockfile
5
+
6
+ # Extend the parser to normalize module names in old .lock files, converting / to -
7
+ class Parser < Librarian::Lockfile::Parser
8
+
9
+ include Librarian::Puppet::Util
10
+
11
+ def extract_and_parse_sources(lines)
12
+ sources = super
13
+ sources.each do |source|
14
+ source[:manifests] = Hash[source[:manifests].map do |name,manifest|
15
+ [normalize_name(name), manifest]
16
+ end]
17
+ end
18
+ sources
19
+ end
20
+
21
+ def extract_and_parse_dependencies(lines, manifests_index)
22
+ # when looking up in manifests_index normalize the name beforehand
23
+ class << manifests_index
24
+ include Librarian::Puppet::Util
25
+ alias_method :old_lookup, :[]
26
+ define_method(:[]) { |k| self.old_lookup(normalize_name(k)) }
27
+ end
28
+ dependencies = []
29
+ while lines.first =~ /^ {2}([\w\-\/]+)(?: \((.*)\))?$/
30
+ lines.shift
31
+ name, requirement = $1, $2.split(/,\s*/)
32
+ dependencies << environment.dsl_class.dependency_type.new(name, requirement, manifests_index[name].source, 'lockfile')
33
+ end
34
+ dependencies
35
+ end
36
+
37
+ def compile_placeholder_manifests(sources_ast)
38
+ manifests = {}
39
+ sources_ast.each do |source_ast|
40
+ source_type = source_ast[:type]
41
+ source = source_type.from_lock_options(environment, source_ast[:options])
42
+ source_ast[:manifests].each do |manifest_name, manifest_ast|
43
+ manifests[manifest_name] = ManifestPlaceholder.new(
44
+ source,
45
+ manifest_name,
46
+ manifest_ast[:version],
47
+ manifest_ast[:dependencies].map do |k, v|
48
+ environment.dsl_class.dependency_type.new(k, v, nil, manifest_name)
49
+ end
50
+ )
51
+ end
52
+ end
53
+ manifests
54
+ end
55
+
56
+ def compile(sources_ast)
57
+ manifests = compile_placeholder_manifests(sources_ast)
58
+ manifests = manifests.map do |name, manifest|
59
+ dependencies = manifest.dependencies.map do |d|
60
+ environment.dsl_class.dependency_type.new(d.name, d.requirement, manifests[d.name].source, name)
61
+ end
62
+ real = Manifest.new(manifest.source, manifest.name)
63
+ real.version = manifest.version
64
+ real.dependencies = manifest.dependencies
65
+ real
66
+ end
67
+ ManifestSet.sort(manifests)
68
+ end
69
+
70
+ end
71
+
72
+ def load(string)
73
+ Parser.new(environment).parse(string)
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ require 'librarian/resolver'
2
+
3
+ module Librarian
4
+ module Puppet
5
+ class Resolver < Librarian::Resolver
6
+
7
+ class Implementation < Librarian::Resolver::Implementation
8
+ def sourced_dependency_for(dependency)
9
+ return dependency if dependency.source
10
+
11
+ source = dependency_source_map[dependency.name] || default_source
12
+ dependency.class.new(dependency.name, dependency.requirement, source, dependency.parent)
13
+ end
14
+ end
15
+
16
+ def implementation(spec)
17
+ Implementation.new(self, spec, :cyclic => cyclic)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1 @@
1
+ require 'librarian/puppet'
@@ -0,0 +1,158 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+ require 'librarian/puppet/util'
4
+ require 'librarian/puppet/source/repo'
5
+
6
+ module Librarian
7
+ module Puppet
8
+ module Source
9
+ class Forge
10
+ class Repo < Librarian::Puppet::Source::Repo
11
+ include Librarian::Puppet::Util
12
+
13
+ def versions
14
+ return @versions if @versions
15
+ @versions = get_versions
16
+ if @versions.empty?
17
+ info { "No versions found for module #{name}" }
18
+ else
19
+ debug { " Module #{name} found versions: #{@versions.join(", ")}" }
20
+ end
21
+ @versions
22
+ end
23
+
24
+ # fetch list of versions ordered for newer to older
25
+ def get_versions
26
+ # implement in subclasses
27
+ end
28
+
29
+ # return map with dependencies in the form {module_name => version,...}
30
+ # version: Librarian::Manifest::Version
31
+ def dependencies(version)
32
+ # implement in subclasses
33
+ end
34
+
35
+ # return the url for a specific version tarball
36
+ # version: Librarian::Manifest::Version
37
+ def url(name, version)
38
+ # implement in subclasses
39
+ end
40
+
41
+ def manifests
42
+ versions.map do |version|
43
+ Manifest.new(source, name, version)
44
+ end
45
+ end
46
+
47
+ def install_version!(version, install_path)
48
+ if environment.local? && !vendored?(name, version)
49
+ raise Error, "Could not find a local copy of #{name} at #{version}."
50
+ end
51
+
52
+ if environment.vendor?
53
+ vendor_cache(name, version) unless vendored?(name, version)
54
+ end
55
+
56
+ cache_version_unpacked! version
57
+
58
+ if install_path.exist? && rsync? != true
59
+ install_path.rmtree
60
+ end
61
+
62
+ unpacked_path = version_unpacked_cache_path(version).join(module_name(name))
63
+
64
+ unless unpacked_path.exist?
65
+ raise Error, "#{unpacked_path} does not exist, something went wrong. Try removing it manually"
66
+ else
67
+ cp_r(unpacked_path, install_path)
68
+ end
69
+
70
+ end
71
+
72
+ def cache_version_unpacked!(version)
73
+ path = version_unpacked_cache_path(version)
74
+ return if path.directory?
75
+
76
+ # The puppet module command is only available from puppet versions >= 2.7.13
77
+ #
78
+ # Specifying the version in the gemspec would force people to upgrade puppet while it's still usable for git
79
+ # So we do some more clever checking
80
+ #
81
+ # Executing older versions or via puppet-module tool gives an exit status = 0 .
82
+ #
83
+ check_puppet_module_options
84
+
85
+ path.mkpath
86
+
87
+ target = vendored?(name, version) ? vendored_path(name, version).to_s : name
88
+
89
+ # can't pass the default v3 forge url (http://forgeapi.puppetlabs.com)
90
+ # to clients that use the v1 API (https://forge.puppet.com)
91
+ # nor the other way around
92
+ module_repository = source.uri.to_s
93
+
94
+ if Forge.client_api_version() > 1 and module_repository =~ %r{^http(s)?://forge\.puppetlabs\.com}
95
+ module_repository = "https://forgeapi.puppetlabs.com"
96
+ warn { "Replacing Puppet Forge API URL to use v3 #{module_repository} as required by your client version #{Librarian::Puppet.puppet_version}" }
97
+ end
98
+
99
+ m = module_repository.match(%r{^http(s)?://forge(api)?\.puppetlabs\.com})
100
+ if Forge.client_api_version() == 1 and m
101
+ ssl = m[1]
102
+ # Puppet 2.7 can't handle the 302 returned by the https url, so stick to http
103
+ if ssl and Librarian::Puppet::puppet_gem_version < Gem::Version.create('3.0.0')
104
+ warn { "Using plain http as your version of Puppet #{Librarian::Puppet::puppet_gem_version} can't download from forge.puppetlabs.com using https" }
105
+ ssl = nil
106
+ end
107
+ module_repository = "http#{ssl}://forge.puppetlabs.com"
108
+ end
109
+
110
+ command = %W{puppet module install --version #{version} --target-dir}
111
+ command.push(*[path.to_s, "--module_repository", module_repository, "--modulepath", path.to_s, "--module_working_dir", path.to_s, "--ignore-dependencies", target])
112
+ debug { "Executing puppet module install for #{name} #{version}: #{command.join(" ").gsub(module_repository, source.to_s)}" }
113
+
114
+ begin
115
+ Librarian::Posix.run!(command)
116
+ rescue Posix::CommandFailure => e
117
+ # Rollback the directory if the puppet module had an error
118
+ begin
119
+ path.unlink
120
+ rescue => u
121
+ debug("Unable to rollback path #{path}: #{u}")
122
+ end
123
+ tar = Dir[File.join(path.to_s, "**/*.tar.gz")]
124
+ msg = ""
125
+ if e.message =~ /Unexpected EOF in archive/ and !tar.empty?
126
+ file = tar.first
127
+ msg = " (looks like an incomplete download of #{file})"
128
+ end
129
+ raise Error, "Error executing puppet module install#{msg}. Check that this command succeeds:\n#{command.join(" ")}\nError:\n#{e.message}"
130
+ end
131
+
132
+ end
133
+
134
+ def check_puppet_module_options
135
+ min_version = Gem::Version.create('2.7.13')
136
+
137
+ if Librarian::Puppet.puppet_gem_version < min_version
138
+ raise Error, "To get modules from the forge, we use the puppet faces module command. For this you need at least puppet version 2.7.13 and you have #{Librarian::Puppet.puppet_version}"
139
+ end
140
+ end
141
+
142
+ def vendor_cache(name, version)
143
+ url = url(name, version)
144
+ path = vendored_path(name, version).to_s
145
+ debug { "Downloading #{url} into #{path}"}
146
+ environment.vendor!
147
+ File.open(path, 'wb') do |f|
148
+ URI.open(url, 'rb') do |input|
149
+ f.write(input.read)
150
+ end
151
+ end
152
+ end
153
+
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,92 @@
1
+ require 'json'
2
+ require 'open-uri'
3
+ require 'librarian/puppet/source/forge/repo'
4
+
5
+ module Librarian
6
+ module Puppet
7
+ module Source
8
+ class Forge
9
+ class RepoV1 < Librarian::Puppet::Source::Forge::Repo
10
+
11
+ def initialize(source, name)
12
+ super(source, name)
13
+ # API returned data for this module including all versions and dependencies, indexed by module name
14
+ # from https://forge.puppetlabs.com/api/v1/releases.json?module=#{name}
15
+ @api_data = nil
16
+ # API returned data for this module and a specific version, indexed by version
17
+ # from https://forge.puppetlabs.com/api/v1/releases.json?module=#{name}&version=#{version}
18
+ @api_version_data = {}
19
+ end
20
+
21
+ def get_versions
22
+ api_data(name).map { |r| r['version'] }.reverse
23
+ end
24
+
25
+ def dependencies(version)
26
+ api_version_data(name, version)['dependencies']
27
+ end
28
+
29
+ def url(name, version)
30
+ info = api_version_data(name, version)
31
+ "#{source}#{info[name].first['file']}"
32
+ end
33
+
34
+ private
35
+
36
+ # convert organization/modulename to organization-modulename
37
+ def normalize_dependencies(data)
38
+ return nil if data.nil?
39
+ # convert organization/modulename to organization-modulename
40
+ data.keys.each do |m|
41
+ if m =~ %r{.*/.*}
42
+ data[normalize_name(m)] = data[m]
43
+ data.delete(m)
44
+ end
45
+ end
46
+ data
47
+ end
48
+
49
+ # get and cache the API data for a specific module with all its versions and dependencies
50
+ def api_data(module_name)
51
+ return @api_data[module_name] if @api_data
52
+ # call API and cache data
53
+ @api_data = normalize_dependencies(api_call(module_name))
54
+ if @api_data.nil?
55
+ raise Error, "Unable to find module '#{name}' on #{source}"
56
+ end
57
+ @api_data[module_name]
58
+ end
59
+
60
+ # get and cache the API data for a specific module and version
61
+ def api_version_data(module_name, version)
62
+ # if we already got all the versions, find in cached data
63
+ return @api_data[module_name].detect{|x| x['version'] == version.to_s} if @api_data
64
+ # otherwise call the api for this version if not cached already
65
+ @api_version_data[version] = normalize_dependencies(api_call(name, version)) if @api_version_data[version].nil?
66
+ @api_version_data[version]
67
+ end
68
+
69
+ def api_call(module_name, version=nil)
70
+ url = source.uri.clone
71
+ url.path += "#{'/' if url.path.empty? or url.path[-1] != '/'}api/v1/releases.json"
72
+ url.query = "module=#{module_name.sub('-','/')}" # v1 API expects "organization/module"
73
+ url.query += "&version=#{version}" unless version.nil?
74
+ debug { "Querying Forge API for module #{name}#{" and version #{version}" unless version.nil?}: #{url}" }
75
+
76
+ begin
77
+ data = URI.open(url) {|f| f.read}
78
+ JSON.parse(data)
79
+ rescue OpenURI::HTTPError => e
80
+ case e.io.status[0].to_i
81
+ when 404,410
82
+ nil
83
+ else
84
+ raise e, "Error requesting #{url}: #{e.to_s}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,100 @@
1
+ require 'librarian/puppet/source/forge/repo'
2
+ require 'puppet_forge'
3
+ require 'librarian/puppet/version'
4
+
5
+ module Librarian
6
+ module Puppet
7
+ module Source
8
+ class Forge
9
+ class RepoV3 < Librarian::Puppet::Source::Forge::Repo
10
+
11
+ PuppetForge.user_agent = "librarian-puppet/#{Librarian::Puppet::VERSION}"
12
+
13
+ def initialize(source, name)
14
+ PuppetForge.host = source.uri.clone
15
+ super(source, name)
16
+ end
17
+
18
+ def get_versions
19
+ get_module.releases.select{|r| r.deleted_at.nil?}.map{|r| r.version}
20
+ end
21
+
22
+ def dependencies(version)
23
+ array = get_release(version).metadata[:dependencies].map{|d| [d[:name], d[:version_requirement]]}
24
+ Hash[*array.flatten(1)]
25
+ end
26
+
27
+ def url(name, version)
28
+ if name == "#{get_module().owner.username}/#{get_module().name}"
29
+ release = get_release(version)
30
+ else
31
+ # should never get here as we use one repo object for each module (to be changed in the future)
32
+ debug { "Looking up url for #{name}@#{version}" }
33
+ release = PuppetForge::V3::Release.find("#{name}-#{version}")
34
+ end
35
+ "#{source}#{release.file_uri}"
36
+ end
37
+
38
+ private
39
+
40
+ def get_module
41
+ begin
42
+ @module ||= PuppetForge::V3::Module.find(name)
43
+ rescue Faraday::ResourceNotFound => e
44
+ raise(Error, "Unable to find module '#{name}' on #{source}")
45
+ end
46
+ @module
47
+ end
48
+
49
+ def get_release(version)
50
+ release = get_module.releases.find{|r| r.version == version.to_s}
51
+ if release.nil?
52
+ versions = get_module.releases.map{|r| r.version}
53
+ raise Error, "Unable to find version '#{version}' for module '#{name}' on #{source} amongst #{versions}"
54
+ end
55
+ release
56
+ end
57
+
58
+ def cache_version_unpacked!(version)
59
+ path = version_unpacked_cache_path(version)
60
+ return if path.directory?
61
+
62
+ path.mkpath
63
+
64
+ target = vendored?(name, version) ? vendored_path(name, version).to_s : name
65
+
66
+ # can't pass the default v3 forge url (http://forgeapi.puppetlabs.com)
67
+ # to clients that use the v1 API (https://forge.puppet.com)
68
+ # nor the other way around
69
+ module_repository = source.uri.to_s
70
+
71
+ if Forge.client_api_version() > 1 and module_repository =~ %r{^http(s)?://forge\.puppetlabs\.com}
72
+ module_repository = "https://forgeapi.puppetlabs.com"
73
+ warn { "Replacing Puppet Forge API URL to use v3 #{module_repository} as required by your client version #{Librarian::Puppet.puppet_version}" }
74
+ end
75
+
76
+ tar_dst = environment.tmp_path.join("#{target}-#{version}.tar.gz")
77
+ dest_dir = path.join(module_name(name))
78
+ tmp_dir = environment.tmp_path.join("forge").to_s
79
+
80
+ begin
81
+ release = get_release(version)
82
+ release.download(tar_dst)
83
+ release.verify(tar_dst)
84
+ PuppetForge::Unpacker.unpack(tar_dst, dest_dir, tmp_dir)
85
+ rescue => e
86
+ # Rollback the directory if the puppet module had an error
87
+ begin
88
+ path.unlink
89
+ rescue => u
90
+ debug("Unable to rollback path #{path}: #{u}")
91
+ end
92
+ raise Error, "Error downloading module from forge. \nError:\n#{e.message}"
93
+ end
94
+ end
95
+
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end