ra10ke 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22fd36a3a88f9a05d15fb0b4e964e3151e0eff8f
4
- data.tar.gz: f9234354db77a8bf07d29e826c7570ffc6bc29d0
3
+ metadata.gz: 4483e49e41d8b69e98d4730662b79084a670580b
4
+ data.tar.gz: 0bf2f4d16f66755a60f3653852900e46983136f1
5
5
  SHA512:
6
- metadata.gz: 8bba17ade9b5e494e75f604e8ec734c1c5489a127adc366554c2b35c69cade5a3e49d50b725fb036ac03e92e98db0651f18856e6eb2f4d0e1db797f1027636c4
7
- data.tar.gz: 3f59f147b0d1709ae9e3c4aa5cfbd2a2e1770b2d789661b9f501a003b99e4ad7e778b667aeba4166b836ffbfe20547d98838519f2f51433426a4221d5b23c451
6
+ metadata.gz: de78be1d8e3146d55b207bd48f100f00cd36e310f857d3de3d2cb831dc894c63b3196fa1aa75dd8fda9964c46e1f6f0412507ebb22863a2ec8e19a37bf39fb2c
7
+ data.tar.gz: 36087c043bb91a4a4b034fb86fc5695c6d2ea74c4ed5ababcb3737b2477ee9714fe30afb8544741feb73a306deacc41633341a7acbf41a354203480776ca223e
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ 0.3.0
5
+ -----
6
+
7
+ 2017-10-08
8
+
9
+ * [#6](https://github.com/voxpupuli/ra10ke/pull/6) Add a dependency solver based on the Ruby Solve library (thanks to [Jarkko Oranen](https://github.com/oranenj))
10
+ * [#8](https://github.com/voxpupuli/ra10ke/pull/8) Add numeric tag convention support (thanks to [Hervé MARTIN](https://github.com/HerveMARTIN))
11
+
4
12
  0.2.0
5
13
  -----
6
14
 
data/README.md CHANGED
@@ -40,6 +40,18 @@ Ignoring specific modules:
40
40
  Under specific conditions you may not wish to report on specific modules being out of date,
41
41
  to ignore a module create `.r10kignore` file in the same directory as your Puppetfile.
42
42
 
43
+ ### r10k:solve_dependencies
44
+
45
+ Reads the Puppetfile in the current directory and uses the ruby 'solve' library to find
46
+ missing and outdated dependencies based on their metadata.
47
+
48
+ The solver does not allow major version bumps according to SemVer by default. To allow
49
+ major upgrades, call the rake task with any parameter.
50
+
51
+ The rake task will download git modules into the modules/ directory to access their metadata.json.
52
+ It will also cache forge metadata in ̃$XDG_CACHE_DIR/ra10ke.metadata_cache in order to make subsequent
53
+ runs faster.
54
+
43
55
  #### Limitations
44
56
 
45
57
  * It works only with modules from the [Forge](https://forge.puppetlabs.com), and Git.
@@ -0,0 +1,227 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'ra10ke/version'
4
+ require 'git'
5
+ require 'set'
6
+ require 'solve'
7
+ require 'yaml/store'
8
+ require 'semverse/version'
9
+
10
+ # How many versions to fetch from the Forge, at most
11
+ FETCH_LIMIT = 3
12
+
13
+ module Ra10ke::Solve
14
+ class RakeTask < ::Rake::TaskLib
15
+ def initialize(*_args)
16
+ namespace :r10k do
17
+ desc 'Find missing or outdated module dependencies'
18
+ task :solve_dependencies, [:allow_major_bump] do |_t, args|
19
+ require 'r10k/puppetfile'
20
+ require 'r10k/module/git'
21
+ require 'r10k/module/metadata_file'
22
+ require 'puppet_forge'
23
+
24
+ allow_major_bump = false
25
+ allow_major_bump = true if args[:allow_major_bump]
26
+
27
+ # Same as in the dependencies task, but oh well.
28
+ PuppetForge.user_agent = "ra10ke/#{Ra10ke::VERSION}"
29
+ puppetfile = R10K::Puppetfile.new('.')
30
+ puppetfile.load!
31
+
32
+ # ignore file allows for "don't tell me about this"
33
+ ignore_modules = []
34
+ if File.exist?('.r10kignore')
35
+ ignore_modules = File.readlines('.r10kignore').each(&:chomp!)
36
+ end
37
+ # Actual new logic begins here:
38
+ cache = (ENV['XDG_CACHE_DIR'] || File.expand_path('~/.cache'))
39
+ # Metadata cache, since the Forge is slow:
40
+ @metadata_cache = YAML::Store.new File.join(cache, 'ra10ke.metadata_cache')
41
+ # The graph of available module versions
42
+ @graph = Solve::Graph.new
43
+ # Set of modules that we have already added to the graph
44
+ @processed_modules = Set.new
45
+ # The set of "demands" we make of the solver. Will be a list of module names
46
+ # Could also demand certain version constraints to hold, but the code does not do it
47
+ # Can be either "module-name" or ["module-name", "version-constraint"]
48
+ @demands = Set.new
49
+ # List of modules we have in the Puppetfile, as [name, version] pairs
50
+ @current_modules = []
51
+
52
+ puppetfile.modules.each do |puppet_module|
53
+ next if ignore_modules.include? puppet_module.title
54
+ if puppet_module.class == R10K::Module::Forge
55
+ module_name = puppet_module.title.tr('/', '-')
56
+ installed_version = puppet_module.expected_version
57
+ puts "Processing Forge module #{module_name}-#{installed_version}"
58
+ @current_modules << [module_name, installed_version]
59
+ @graph.artifact(module_name, installed_version)
60
+ constraint = '>=0.0.0'
61
+ unless allow_major_bump
62
+ ver = Semverse::Version.new installed_version
63
+ if ver.major.zero?
64
+ constraint = "~>#{installed_version}"
65
+ else
66
+ nver = Semverse::Version.new([ver.major + 1, 0, 0])
67
+ constraint = "<#{nver}"
68
+ end
69
+ end
70
+ puts "...Adding a demand: #{module_name} #{constraint}"
71
+
72
+ @demands.add([module_name, constraint])
73
+ puts '...Fetching latest release version information'
74
+ forge_rel = PuppetForge::Module.find(module_name).current_release
75
+ mod = @graph.artifact(module_name, forge_rel.version)
76
+ puts '...Adding its requirements to the graph'
77
+ meta = get_release_metadata(module_name, forge_rel)
78
+ add_reqs_to_graph(mod, meta)
79
+ end
80
+
81
+ next unless puppet_module.class == R10K::Module::Git
82
+ # This downloads the git module to modules/modulename
83
+ meta = fetch_git_metadata(puppet_module)
84
+ version = get_key_or_sym(meta, :version)
85
+ module_name = puppet_module.title.tr('/', '-')
86
+ @current_modules << [module_name, version]
87
+ # We should add git modules with exact versions, or the system might recommend updating to a
88
+ # Forge version.
89
+ puts "Adding git module #{module_name} to the list of required modules with exact version: #{version}"
90
+ @demands.add([module_name, version])
91
+ mod = @graph.artifact(module_name, version)
92
+ puts "...Adding requirements for git module #{module_name}-#{version}"
93
+ add_reqs_to_graph(mod, meta)
94
+ end
95
+ puts
96
+ puts 'Resolving dependencies...'
97
+ if allow_major_bump
98
+ puts 'WARNING: Potentially breaking updates are allowed for this resolution'
99
+ end
100
+ result = Solve.it!(@graph, @demands, sorted: true)
101
+ puts
102
+ print_module_diff(@current_modules, result)
103
+ end
104
+ end
105
+ end
106
+
107
+ def get_release_metadata(name, release)
108
+ meta = nil
109
+ @metadata_cache.transaction do
110
+ meta = @metadata_cache["#{name}-#{release.version}"]
111
+ unless meta
112
+ meta = release.metadata
113
+ @metadata_cache["#{name}-#{release.version}"] = meta
114
+ end
115
+ end
116
+ meta
117
+ end
118
+
119
+ def fetch_git_metadata(puppet_module)
120
+ # No caching here. I don't think it's really possible to do in a sane way.
121
+ puts "Fetching git module #{puppet_module.title}, saving to modules/"
122
+ puppet_module.sync
123
+ metadata_path = Pathname.new(puppet_module.full_path) + 'metadata.json'
124
+ unless metadata_path.exist?
125
+ puts 'WARNING: metadata.json does not exist, assuming version 0.0.0 and no dependencies'
126
+ return {
127
+ version: '0.0.0',
128
+ name: puppet_module.title,
129
+ dependencies: []
130
+ }
131
+ end
132
+ metadata = R10K::Module::MetadataFile.new(metadata_path)
133
+ metadata = metadata.read
134
+ {
135
+ version: metadata.version,
136
+ name: metadata.name,
137
+ dependencies: metadata.dependencies
138
+ }
139
+ end
140
+
141
+ # Is there a better way? :(
142
+ def get_key_or_sym(hash, k)
143
+ hash.fetch(k.to_sym, hash.fetch(k.to_s, nil))
144
+ end
145
+
146
+ # At least puppet-extlib has malformed metadata
147
+ def get_version_req(dep)
148
+ req = get_key_or_sym(dep, :version_requirement)
149
+ req = get_key_or_sym(dep, :version_range) unless req
150
+ req
151
+ end
152
+
153
+ def print_module_diff(current, resolution)
154
+ current.sort!
155
+ resolution.sort!
156
+ outdated = []
157
+ missing = []
158
+ resolution.each do |mod|
159
+ cur_mod, cur_version = current.shift
160
+ mod, version = mod
161
+ if (cur_mod == mod) && cur_version && (cur_version != version)
162
+ outdated << [mod, cur_version, version]
163
+ elsif cur_mod != mod
164
+ missing << [mod, version]
165
+ current.unshift [cur_mod, cur_version]
166
+ end
167
+ end
168
+ missing.each do |m|
169
+ puts format('MISSING: %-25s %s', *m)
170
+ end
171
+ outdated.each do |o|
172
+ puts format('OUTDATED: %-25s %s -> %s', *o)
173
+ end
174
+ end
175
+
176
+ def add_reqs_to_graph(artifact, metadata, no_demands = nil)
177
+ deps = get_key_or_sym(metadata, :dependencies)
178
+ my_name = get_key_or_sym(metadata, :name)
179
+ deps.each do |dep|
180
+ name = get_key_or_sym(dep, :name).tr('/', '-')
181
+ # Add dependency to the global set of modules we want, so that we can
182
+ # actually ask the solver for the versioned thing
183
+ @demands.add(name) unless no_demands
184
+ ver = get_version_req(dep)
185
+ unless ver
186
+ # no version specified, so anything will do
187
+ ver = '>=0.0.0'
188
+ end
189
+ ver.split(/(?=[<])/).each do |bound|
190
+ bound.strip!
191
+ v = begin
192
+ Semverse::Constraint.new(bound)
193
+ rescue
194
+ nil
195
+ end
196
+ if v
197
+ artifact.depends(name, v.to_s)
198
+ else
199
+ puts "WARNING: Invalid version constraint: #{bound}"
200
+ end
201
+ end
202
+ # Find the dependency in the forge, unless it's already been processed
203
+ # and add its releases to the global graph
204
+ next unless @processed_modules.add?(name)
205
+ puts "Fetching module info for #{name}"
206
+ mod = begin
207
+ PuppetForge::Module.find(name)
208
+ rescue
209
+ # It's probably a git module
210
+ nil
211
+ end
212
+ next unless mod # Git module, or non-forge dependency. Skip to next for now.
213
+ # Fetching metadata for all releases takes ages (which is weird, since it's mostly static info)
214
+ mod.releases.take(FETCH_LIMIT).each do |rel|
215
+ meta = get_release_metadata(name, rel)
216
+ rel_artifact = @graph.artifact(name, rel.version)
217
+ puts "...Recursively adding requirements for dependency #{name} version #{rel.version}"
218
+ # We don't want to add the requirements to the list of demands for all versions,
219
+ # but we need them in the graph to be able to solve dependencies
220
+ add_reqs_to_graph(rel_artifact, meta, :no_demands)
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ Ra10ke::Solve::RakeTask.new
@@ -1,3 +1,3 @@
1
1
  module Ra10ke
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/ra10ke.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'rake'
2
2
  require 'rake/tasklib'
3
3
  require 'ra10ke/version'
4
+ require 'ra10ke/solve'
4
5
  require 'git'
5
6
 
6
7
  module Ra10ke
@@ -52,12 +53,14 @@ module Ra10ke
52
53
  when 'sha'
53
54
  latest_ref = remote_refs['head'][:sha]
54
55
  when 'tag'
55
- # we have to be opinionated here, due to multiple conventions only one can reign suppreme
56
- # as such tag v#.#.# is what will pick.
57
- if ref.match(/^[vV]\d/)
56
+ # we have to be opinionated here, due to multiple conventions only the two main will be accepted
57
+ # v#.#.# or #.#.# is what we will pick.
58
+ if ref.match(/^[vV]?\d[\.\d]*/)
58
59
  tags = remote_refs['tags']
59
- version_tags = tags.select { |f| /^[vV]\d/.match(f) }
60
+ version_tags = tags.select { |f| /^[vV]?\d[\.\d]*/.match(f) }
60
61
  latest_ref = version_tags.keys.sort.last
62
+ else
63
+ latest_ref = "undef (tags don't match v#.#.# or #.#.#)"
61
64
  end
62
65
  else
63
66
  raise "Unable to determine ref_type for #{puppet_module.title}"
data/ra10ke.gemspec CHANGED
@@ -19,4 +19,5 @@ Gem::Specification.new do |spec|
19
19
  spec.add_dependency "puppet_forge"
20
20
  spec.add_dependency "r10k"
21
21
  spec.add_dependency "git"
22
+ spec.add_dependency "solve"
22
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ra10ke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Chatzimichos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-03 00:00:00.000000000 Z
11
+ date: 2017-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: solve
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: R10K and Puppetfile rake tasks
70
84
  email:
71
85
  - tampakrap@gmail.com
@@ -79,6 +93,7 @@ files:
79
93
  - LICENSE.txt
80
94
  - README.md
81
95
  - lib/ra10ke.rb
96
+ - lib/ra10ke/solve.rb
82
97
  - lib/ra10ke/version.rb
83
98
  - ra10ke.gemspec
84
99
  homepage: https://github.com/voxpupuli/ra10ke
@@ -101,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
116
  version: '0'
102
117
  requirements: []
103
118
  rubyforge_project:
104
- rubygems_version: 2.4.5.1
119
+ rubygems_version: 2.6.11
105
120
  signing_key:
106
121
  specification_version: 4
107
122
  summary: Syntax check for the Puppetfile, check for outdated installed puppet modules