puppet_metadata 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1b9da6033c7aa055e1e88289211ad3356764ea737184fd594fce1c31e1e94c27
4
+ data.tar.gz: 8fdfd523619505b15568769eb276424cffcc5c257c3a08377ee8a7abd8571701
5
+ SHA512:
6
+ metadata.gz: 574fb26918bdd3c5b7a7ccad604557cb130797f704c87102d552fe3ef5a121acd15d4be22ba67bd273ba3d0d35a4c5b44c1ed8fe48e09b019da3785f9a149a9f
7
+ data.tar.gz: fcf0a1b46a59fdfbd3f6269f430495a63acbfc387c187658b25b4466fd8c0a0b5835cf221a5f6b7fcfdcd8f15208ab28141815edb8701666f9cefc47a422f99b
@@ -0,0 +1,3 @@
1
+ # puppet_metadata
2
+
3
+ The gem intends to provide an abstraction over Puppet's metadata.json file. Its API allow easy iteration over its illogical data structures.
@@ -0,0 +1,20 @@
1
+ # A module that provides abstractions around Puppet's metadata format.
2
+ module PuppetMetadata
3
+ autoload :Beaker, 'puppet_metadata/beaker'
4
+ autoload :Metadata, 'puppet_metadata/metadata'
5
+ autoload :OperatingSystem, 'puppet_metadata/operatingsystem'
6
+
7
+ # Parse a JSON encoded metadata string
8
+ # @param data A JSON encoded metadata string
9
+ # @return [PuppetMetadata::Metadata] A Metadata object
10
+ def self.parse(data)
11
+ Metadata.new(JSON.parse(data))
12
+ end
13
+
14
+ # Read and parse a path containing metadata
15
+ # @param path The path metadata.json
16
+ # @return [PuppetMetadata::Metadata] A Metadata object
17
+ def self.read(path)
18
+ parse(File.read(path))
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module PuppetMetadata
2
+ # A class to provide abstractions for integration with beaker
3
+ #
4
+ # @see https://rubygems.org/gems/beaker
5
+ # @see https://rubygems.org/gems/beaker-hostgenerator
6
+ class Beaker
7
+ # Convert an Operating System name with a release to a Beaker setfile
8
+ #
9
+ # @param [String] os
10
+ # The Operating System string as metadata.json knows it, which in turn is
11
+ # based on Facter's operatingsystem fact.
12
+ # @param [String] release The OS release
13
+ # @param [Boolean] use_fqdn
14
+ # Whether or not to use a FQDN, ensuring a domain
15
+ # @param [Boolean] pidfile_workaround
16
+ # Whether or not to apply the systemd PIDFile workaround. This is only
17
+ # needed when the daemon uses PIDFile in its service file and using
18
+ # Docker as a Beaker hypervisor. This is to work around Docker's
19
+ # limitations.
20
+ #
21
+ # @return [String] The beaker setfile description or nil
22
+ def self.os_release_to_setfile(os, release, use_fqdn: false, pidfile_workaround: false)
23
+ return unless os_supported?(os)
24
+
25
+ name = "#{os.downcase}#{release.tr('.', '')}-64"
26
+
27
+ options = {}
28
+ options[:hostname] = "#{name}.example.com" if use_fqdn
29
+ # Docker messes up cgroups and modern systemd can't deal with that when
30
+ # PIDFile is used.
31
+ if pidfile_workaround
32
+ case os
33
+ when 'CentOS'
34
+ options[:image] = 'centos:7.6.1810' if release == '7'
35
+ when 'Ubuntu'
36
+ options[:image] = 'ubuntu:xenial-20191212' if release == '16.04'
37
+ end
38
+ end
39
+
40
+ setfile = name
41
+ setfile += "{#{options.map { |key, value| "#{key}=#{value}" }.join(',')}}" if options.any?
42
+ setfile
43
+ end
44
+
45
+ # Return whether a Beaker setfile can be generated for the given OS
46
+ # @param [String] os The operating system
47
+ def self.os_supported?(os)
48
+ ['CentOS', 'Fedora', 'Debian', 'Ubuntu'].include?(os)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,210 @@
1
+ require 'json'
2
+
3
+ module PuppetMetadata
4
+ # An exception indicating the metadata is invalid
5
+ #
6
+ # @attr_reader [Array[Hash[Symbol, String]]] errors
7
+ # The errors in the metadata
8
+ class InvalidMetadataException < Exception
9
+ attr_reader :errors
10
+
11
+ # Returns a new instance of InvalidMetadataException
12
+ # @param [Array[Hash[Symbol, String]]] errors
13
+ # The errors in the metadata
14
+ def initialize(errors)
15
+ messages = errors.map { |error| error[:message] }
16
+ super("Invalid metadata: #{messages.join(', ')}")
17
+ @errors = errors
18
+ end
19
+ end
20
+
21
+ # An abstraction over Puppet metadata
22
+ class Metadata
23
+ attr_reader :metadata
24
+
25
+ # @param [Hash[String, Any]] metadata
26
+ # The metadata as a data structure
27
+ # @param [Boolean] validate
28
+ # Whether to validate the metadata using metadata-json-lint
29
+ # @raise [PuppetMetadata::InvalidMetadataException]
30
+ # When the provided metadata is invalid according to metadata.json-lint
31
+ # @see PuppetMetadata.parse
32
+ # @see PuppetMetadata.read
33
+ # @see https://rubygems.org/gems/metadata-json-lint
34
+ def initialize(metadata, validate = true)
35
+ if validate
36
+ require 'metadata_json_lint'
37
+ schema_errors = MetadataJsonLint::Schema.new.validate(metadata)
38
+ raise InvalidMetadataException.new(schema_errors) if schema_errors.any?
39
+ end
40
+
41
+ @metadata = metadata
42
+ end
43
+
44
+ # The name
45
+ # @return [String] The name
46
+ def name
47
+ metadata['name']
48
+ end
49
+
50
+ # The version
51
+ # @return [String]
52
+ def version
53
+ metadata['version']
54
+ end
55
+
56
+ # The license
57
+ # @return [String] The license
58
+ def license
59
+ metadata['license']
60
+ end
61
+
62
+ # @return [Hash[String, Array[String]]]
63
+ # The supported operating system and its major releases
64
+ def operatingsystems
65
+ @operatingsystems ||= begin
66
+ return {} if metadata['operatingsystem_support'].nil?
67
+
68
+ supported = metadata['operatingsystem_support'].map do |os|
69
+ next unless os['operatingsystem']
70
+ [os['operatingsystem'], os['operatingsystemrelease']]
71
+ end
72
+
73
+ Hash[supported.compact]
74
+ end
75
+ end
76
+
77
+ # Returns whether an operating system's release is supported
78
+ #
79
+ # @param [String] operatingsystem The operating system
80
+ # @param [String] release The major release of the OS
81
+ # @return [Boolean] Whether an operating system's release is supported
82
+ def os_release_supported?(operatingsystem, release)
83
+ # If no OS is present, everything is supported. An example of this is
84
+ # modules with only functions.
85
+ return true if operatingsystems.empty?
86
+
87
+ # if the key present, nil indicates all releases are supported
88
+ return false unless operatingsystems.key?(operatingsystem)
89
+
90
+ releases = operatingsystems[operatingsystem]
91
+ releases.nil? || releases.include?(release)
92
+ end
93
+
94
+ # @param [Date] at The date to check the EOL time. Today is used when nil.
95
+ # @return [Hash[String, Array[String]]]
96
+ def eol_operatingsystems(at = nil)
97
+ at ||= Date.today
98
+
99
+ unsupported = operatingsystems.map do |os, rels|
100
+ next unless rels
101
+ eol = rels.select { |rel| OperatingSystem.eol?(os, rel, at) }
102
+ [os, eol] if eol.any?
103
+ end
104
+
105
+ Hash[unsupported.compact]
106
+ end
107
+
108
+ # A hash representation of the requirements
109
+ #
110
+ # Every element in the original array is converted. The name is used as a
111
+ # key while version_requirement is used as a value. No value indicates any
112
+ # version is accepted.
113
+ #
114
+ # @see #satisfies_requirement?
115
+ # @return [Hash[String, SemanticPuppet::VersionRange]]
116
+ # The requirements of the module
117
+ #
118
+ # @example
119
+ # metadata = Metadata.new(data)
120
+ # metadata.requirements.each do |requirement, version_requirement|
121
+ # if version_requirement
122
+ # puts "#{metadata.name} requires #{requirement} #{version_requirement}"
123
+ # else
124
+ # puts "#{metadata.name} requires any #{requirement}"
125
+ # end
126
+ # end
127
+ def requirements
128
+ @requirements ||= build_version_requirement_hash(metadata['requirements'])
129
+ end
130
+
131
+ # @param [String] name The name of the requirement
132
+ # @param [String] version The version of the requirement
133
+ # @see #requirements
134
+ def satisfies_requirement?(name, version)
135
+ matches?(requirements[name], version)
136
+ end
137
+
138
+ # A hash representation of the dependencies
139
+ #
140
+ # Every element in the original array is converted. The name is used as a
141
+ # key while version_requirement is used as a value. No value indicates any
142
+ # version is accepted.
143
+ #
144
+ # @see #satisfies_dependency?
145
+ # @return [Hash[String, SemanticPuppet::VersionRange]]
146
+ # The dependencies of the module
147
+ #
148
+ # @example
149
+ # metadata = Metadata.new(data)
150
+ # metadata.dependencies.each do |os, releases|
151
+ # if releases
152
+ # releases.each do |release|
153
+ # puts "#{metadata.name} supports #{os} #{release}"
154
+ # end
155
+ # else
156
+ # puts "#{metadata.name} supports all #{os} releases"
157
+ # end
158
+ # end
159
+ def dependencies
160
+ @dependencies ||= build_version_requirement_hash(metadata['dependencies'])
161
+ end
162
+
163
+ # @param [String] name The name of the dependency
164
+ # @param [String] version The version of the dependency
165
+ # @see #dependencies
166
+ def satisfies_dependency?(name, version)
167
+ matches?(dependencies[name], version)
168
+ end
169
+
170
+ # @param [Boolean] use_fqdn
171
+ # Whether to set the hostname to a fully qualified domain name
172
+ # @param [Boolean] pidfile_workaround
173
+ # Whether to apply the Docker pidfile workaround
174
+ # @yieldparam [String] setfile
175
+ # The supported setfile configurations
176
+ # @see PuppetMetadata::Beaker#os_release_to_setfile
177
+ def beaker_setfiles(use_fqdn: false, pidfile_workaround: false)
178
+ operatingsystems.each do |os, releases|
179
+ next unless PuppetMetadata::Beaker.os_supported?(os)
180
+ releases&.each do |release|
181
+ setfile = PuppetMetadata::Beaker.os_release_to_setfile(os, release, use_fqdn: use_fqdn, pidfile_workaround: pidfile_workaround)
182
+ yield setfile if setfile
183
+ end
184
+ end
185
+ end
186
+
187
+ private
188
+
189
+ def build_version_requirement_hash(array)
190
+ return {} if array.nil?
191
+
192
+ require 'semantic_puppet'
193
+
194
+ reqs = array.map do |requirement|
195
+ next unless requirement['name']
196
+ version_requirement = requirement['version_requirement'] || '>= 0'
197
+ [requirement['name'], SemanticPuppet::VersionRange.parse(version_requirement)]
198
+ end
199
+
200
+ Hash[reqs.compact]
201
+ end
202
+
203
+ def matches?(required_version, provided_version)
204
+ return false unless required_version
205
+
206
+ provided_version = SemanticPuppet::Version.parse(provided_version) if provided_version.is_a?(String)
207
+ required_version.include?(provided_version)
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,159 @@
1
+ require 'date'
2
+
3
+ module PuppetMetadata
4
+ # An abstraction layer over operating systems. Mostly to determine End Of
5
+ # Life dates.
6
+ #
7
+ # @see https://endoflife.software/operating-systems
8
+ class OperatingSystem
9
+ # The EOL dates for the various operating systems
10
+ # @see .eol_date
11
+ EOL_DATES = {
12
+ # https://endoflife.software/operating-systems/linux/centos
13
+ 'CentOS' => {
14
+ '8' => '2029-05-31',
15
+ '7' => '2024-06-30',
16
+ '6' => '2020-11-30',
17
+ '5' => '2017-03-31',
18
+ '4' => '2012-02-29',
19
+ '3' => '2010-10-30',
20
+ },
21
+ # https://endoflife.software/operating-systems/linux/debian
22
+ # https://wiki.debian.org/DebianReleases
23
+ 'Debian' => {
24
+ # TODO: EOL is standard support, not the extended life cycle
25
+ '10' => nil, # '~2022',
26
+ '9' => nil, # '~2020',
27
+ '8' => '2018-06-06',
28
+ '7' => '2016-04-26',
29
+ '6' => '2015-05-31',
30
+ '5' => '2012-02-06',
31
+ '4' => '2010-02-15',
32
+ '3.1' => '2008-03-31',
33
+ '3.0' => '2006-06-30',
34
+ '2.2' => '2003-06-30',
35
+ '2.1' => '2000-09-30',
36
+ },
37
+ # https://endoflife.software/operating-systems/linux/fedora
38
+ 'Fedora' => {
39
+ '32' => nil,
40
+ '31' => nil,
41
+ '30' => nil,
42
+ '29' => '2019-11-26',
43
+ '28' => '2019-05-28',
44
+ '27' => '2018-11-30',
45
+ '26' => '2018-05-29',
46
+ '25' => '2017-12-12',
47
+ '24' => '2017-08-08',
48
+ '23' => '2016-12-20',
49
+ '22' => '2016-07-19',
50
+ '21' => '2015-12-01',
51
+ '20' => '2015-06-23',
52
+ '19' => '2015-01-06',
53
+ '18' => '2014-01-14',
54
+ '17' => '2013-07-30',
55
+ '16' => '2013-02-12',
56
+ '15' => '2012-06-26',
57
+ '14' => '2011-12-08',
58
+ '13' => '2011-06-24',
59
+ '12' => '2010-12-02',
60
+ '11' => '2010-06-25',
61
+ '10' => '2009-12-18',
62
+ '9' => '2009-07-10',
63
+ '8' => '2009-01-07',
64
+ '7' => '2008-06-13',
65
+ '6' => '2007-12-07',
66
+ '5' => '2007-07-02',
67
+ '4' => '2006-08-07',
68
+ '3' => '2006-01-16',
69
+ '2' => '2005-04-11',
70
+ '1' => '2004-09-20',
71
+ },
72
+ # https://endoflife.software/operating-systems/linux/red-hat-enterprise-linux-rhel
73
+ 'RedHat' => {
74
+ # TODO: EOL is standard support, not the extended life cycle
75
+ '8' => '2029-05-31',
76
+ '7' => '2024-06-30',
77
+ '6' => '2020-11-30',
78
+ '5' => '2017-03-31',
79
+ '4' => '2012-02-29',
80
+ '3' => '2010-10-31',
81
+ },
82
+ # https://endoflife.software/operating-systems/linux/ubuntu
83
+ 'Ubuntu' => {
84
+ '20.04' => '2025-04-15',
85
+ '19.10' => '2020-07-15',
86
+ '19.04' => '2020-01-15',
87
+ '18.10' => '2019-07-15',
88
+ '18.04' => '2023-04-15',
89
+ '17.10' => '2018-07-15',
90
+ '17.04' => '2018-01-15',
91
+ '16.10' => '2017-07-20',
92
+ '16.04' => '2021-04-15',
93
+ '15.10' => '2016-07-28',
94
+ '15.04' => '2016-02-04',
95
+ '14.10' => '2015-07-23',
96
+ '14.04' => '2019-04-15',
97
+ '13.10' => '2014-07-17',
98
+ '13.04' => '2014-01-27',
99
+ '12.04' => '2017-04-28',
100
+ '11.10' => '2013-05-09',
101
+ '11.04' => '2012-10-28',
102
+ '10.10' => '2012-04-10',
103
+ '10.04' => '2015-04-30',
104
+ '9.10' => '2011-04-30',
105
+ '9.04' => '2010-10-23',
106
+ '8.10' => '2010-04-30',
107
+ '8.04' => '2013-05-09',
108
+ '7.10' => '2009-04-18',
109
+ '7.04' => '2008-10-19',
110
+ '6.10' => '2008-04-26',
111
+ '6.06' => '2011-06-01',
112
+ '5.10' => '2007-04-13',
113
+ '5.04' => '2006-10-31',
114
+ '4.10' => '2006-04-30',
115
+ },
116
+ }
117
+
118
+ # Return the EOL date for the given operating system release
119
+ # @param [String] operatingsystem
120
+ # The operating system
121
+ # @param [String] release
122
+ # The major version of the operating system
123
+ # @return [optional, Date]
124
+ # The EOL date for the given operating system release. Nil is returned
125
+ # when the either when the OS, the release or the EOL date is unknown
126
+ def self.eol_date(operatingsystem, release)
127
+ releases = EOL_DATES[operatingsystem]
128
+ return unless releases
129
+ date = releases[release]
130
+ return unless date
131
+ Date.parse(date)
132
+ end
133
+
134
+ # Return whether the given operating system release is EOL at the given
135
+ # date
136
+ #
137
+ # @param [String] operatingsystem
138
+ # The operating system
139
+ # @param [String] release
140
+ # The major version of the operating system
141
+ # @return [Boolean]
142
+ # The EOL date for the given operating system release. Nil is returned
143
+ # when the either when the OS, the release or the EOL date is unknown
144
+ def self.eol?(operatingsystem, release, at = nil)
145
+ date = eol_date(operatingsystem, release)
146
+ date && date < (at || Date.today)
147
+ end
148
+
149
+ # Return the latest known release for a given operating system
150
+ # @param [String] operatingsystem The operating system
151
+ # @return [optional, String]
152
+ # The latest major release for the given operating system, if any is
153
+ # known
154
+ def self.latest_release(operatingsystem)
155
+ releases = EOL_DATES[operatingsystem]
156
+ releases&.keys&.max_by { |release| Gem::Version.new(release) }
157
+ end
158
+ end
159
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet_metadata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vox Pupuli
8
+ - Ewoud Kohl van Wijngaarden
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2020-10-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: metadata-json-lint
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '2.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '2.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: semantic_puppet
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '13.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '13.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rspec-its
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rdoc
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '6.0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '6.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: yard
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.9'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.9'
112
+ description: A package that provides abstractions for the Puppet Metadata
113
+ email:
114
+ - voxpupuli@groups.io
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files:
118
+ - README.md
119
+ files:
120
+ - README.md
121
+ - lib/puppet_metadata.rb
122
+ - lib/puppet_metadata/beaker.rb
123
+ - lib/puppet_metadata/metadata.rb
124
+ - lib/puppet_metadata/operatingsystem.rb
125
+ homepage: http://github.com/voxpupuli/puppet_metadata
126
+ licenses:
127
+ - Apache-2.0
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options:
131
+ - "--main"
132
+ - README.md
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.1.2
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Data structures for the Puppet Metadata
150
+ test_files: []