librarian-puppet-rethinc 3.0.1.1

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