Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Commit 0a9f76a

Browse files
committed
Initial code commit
1 parent 09e7ba6 commit 0a9f76a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3019
-0
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.rb eol=lf

.gitignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.git/
2+
.*.sw[op]
3+
.metadata
4+
.yardoc
5+
.yardwarns
6+
*.iml
7+
.bundle/
8+
.idea/
9+
bin/
10+
Gemfile.local
11+
Gemfile.lock
12+
tmp/
13+
vendor/
14+
.DS_Store

.rubocop.yml

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
inherit_from: .rubocop_todo.yml
2+
3+
AllCops:
4+
Include:
5+
- 'lib/**/*.rb'
6+
- 'puppetfile-cli.rb'
7+
Exclude:
8+
- 'tmp/**/*'
9+
- 'spec/**/*'
10+
- 'vendor/**/*'
11+
- Gemfile
12+
- Rakefile
13+
14+
Style/Documentation:
15+
Enabled: false
16+
Style/NumericLiterals:
17+
Enabled: false
18+
19+
# Length is not useful indicator
20+
Metrics/LineLength:
21+
Enabled: false
22+
Metrics/MethodLength:
23+
Enabled: false
24+
Metrics/ModuleLength:
25+
Enabled: false
26+
Metrics/ClassLength:
27+
Enabled: false
28+
Metrics/BlockLength:
29+
Enabled: false
30+
Metrics/AbcSize:
31+
Enabled: false
32+
Metrics/PerceivedComplexity:
33+
Enabled: false
34+
Metrics/CyclomaticComplexity:
35+
Enabled: false
36+
37+
# Empty method definitions over more than one line is ok
38+
Style/EmptyMethod:
39+
Enabled: false
40+
41+
# Either sytnax for regex is ok
42+
Style/RegexpLiteral:
43+
Enabled: false
44+
45+
# Sometimes, I actually want both!
46+
Style/DateTime:
47+
Enabled: false
48+
49+
# Please don't fail at failing.
50+
Lint/RedundantCopDisableDirective:
51+
Enabled: false
52+
53+
# Don't care
54+
Naming/MemoizedInstanceVariableName:
55+
Enabled: false
56+
Layout/EmptyLineAfterGuardClause:
57+
Enabled: false
58+
Metrics/ParameterLists:
59+
Enabled: false
60+
61+
# In some cases readability is better without these cops enabled
62+
Style/ConditionalAssignment:
63+
Enabled: false
64+
Style/Next:
65+
Enabled: false
66+
67+
# Enforce LF line endings, even when on Windows
68+
Layout/EndOfLine:
69+
EnforcedStyle: lf
70+
71+
# We only alias for monkey patching
72+
Style/Alias:
73+
Enabled: false
74+
75+
# Harder to read when on
76+
Style/SymbolProc:
77+
Enabled: false
78+
Style/HashSyntax:
79+
Enabled: false
80+
81+
Layout/AlignHash:
82+
EnforcedHashRocketStyle: table
83+
84+
Naming/RescuedExceptionsVariableName:
85+
PreferredName: e
86+
87+
# There's no benefit to this and can make things harder to read.
88+
Style/NumericPredicate:
89+
Enabled: false
90+
91+
# This is not valid on Ruby 2.1
92+
Style/SafeNavigation:
93+
Enabled: false

.rubocop_todo.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This configuration was generated by
2+
# `rubocop --auto-gen-config`
3+
# on 2019-11-04 20:38:19 +0800 using RuboCop version 0.76.0.
4+
# The point is for the user to remove these configuration records
5+
# one by one as the offenses are removed from the code base.
6+
# Note that changes in the inspected code, or installation of new
7+
# versions of RuboCop, may require this file to be generated again.
8+
9+
# Offense count: 2
10+
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
11+
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
12+
Naming/FileName:
13+
Exclude:
14+
- 'lib/puppetfile-resolver.rb'
15+
- 'puppetfile-cli.rb'

.travis.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
dist: bionic
3+
language: ruby
4+
cache: bundler
5+
before_install:
6+
- bundle -v
7+
- rm -f Gemfile.lock
8+
- gem update --system $RUBYGEMS_VERSION
9+
- gem --version
10+
- bundle -v
11+
script:
12+
- 'bundle exec $CHECK'
13+
bundler_args: --without system_tests
14+
rvm:
15+
- 2.5.3
16+
stages:
17+
- spec
18+
matrix:
19+
include:
20+
-
21+
env: CHECK="rubocop"
22+
stage: spec
23+
-
24+
env: CHECK="rspec"
25+
stage: spec
26+
branches:
27+
only:
28+
- master

Gemfile

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
source ENV['GEM_SOURCE'] || 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in pdk.gemspec
4+
gemspec
5+
6+
group :development do
7+
gem 'rake', '>= 10.4', :require => false
8+
gem 'rspec', '>= 3.2', :require => false
9+
10+
if RUBY_VERSION =~ /^2\.1\./
11+
gem "rubocop", "<= 0.57.2", :require => false, :platforms => [:ruby, :x64_mingw]
12+
else
13+
gem "rubocop", ">= 0.60.0", :require => false, :platforms => [:ruby, :x64_mingw]
14+
end
15+
end
16+
17+
# Evaluate Gemfile.local and ~/.gemfile if they exist
18+
extra_gemfiles = [
19+
"#{__FILE__}.local",
20+
File.join(Dir.home, '.gemfile'),
21+
]
22+
23+
extra_gemfiles.each do |gemfile|
24+
if File.file?(gemfile) && File.readable?(gemfile)
25+
eval(File.read(gemfile), binding)
26+
end
27+
end
28+
# vim: syntax=ruby

README.md

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
[![Build Status](https://travis-ci.com/lingua-pupuli/puppetfile-resolver.svg?branch=master)](https://travis-ci.com/lingua-pupuli/puppetfile-resolver)
2+
3+
# Puppetfile Resolver
4+
5+
The [Puppetfile](https://puppet.com/docs/pe/latest/puppetfile.html) is used by Puppet to manage the collection of modules used by a Puppet master. The Puppetfile is then used by tools like [R10K](https://github.com/puppetlabs/r10k) and [Code Manager](https://puppet.com/docs/pe/latest/code_mgr_how_it_works.html#how-code-manager-works) to download and install the required modules.
6+
7+
However, the Puppetfile is designed to have explicit dependencies, that is, **all** modules and **all** of the dependencies must be specified in Puppetfile. This is very different to formats like `Gemfile` (Ruby) or `package.json` (NodeJS) where dependencies are brought in as needed.
8+
9+
Using explicit dependencies is great in a configuration management system like Puppet, but it puts the burden on updates onto the user.
10+
11+
This library includes all of the code to parse a Puppetfile and then calculate a dependency graph to try and resolve all of the module dependencies and versions. The resolver can also restrict on Puppet version, for example, only Modules which support Puppet 6.
12+
13+
**Note** This is still in active development. Git history may be re-written until an initial version is released
14+
15+
## To Do
16+
17+
- Could do with more tests
18+
- Add YARD documentation
19+
20+
## Why a library and not a CLI tool?
21+
22+
Due to all of the different needs of tool developers, this is offered as a library instead of full blown CLI tool. For example, the needs of a VSCode Extensions developer are very different than that of the Puppet Developer Kit developer.
23+
24+
Therefore this is a library which is intended to be used by tool developers to create useful tools for users, and not really for direct use by users.
25+
26+
Note that a CLI is included (`puppetfile-cli.rb`) only as an example of how to create a tool using this library.
27+
28+
## Architecture
29+
30+
``` text
31+
+-----------------+ +-----------------+ +-----------------+
32+
| Forge Searcher | | Github Searcher | | Local Searcher |
33+
+-------+---------+ +--------+--------+ +-------+---------+
34+
| | |
35+
+----------------------+--------------------+
36+
|
37+
|
38+
V
39+
+--------+ +----------+ +-------------------+
40+
-- Text --> | Parser | -- Document Model -+-> | Resolver | -- Dependency Graph -+-> | Resolution Result |
41+
+--------+ | +----------+ | +-------------------+
42+
| |
43+
| |
44+
V V
45+
+-----------+ +------------+
46+
| Document | | Resolution |
47+
| Validator | | Validator |
48+
+-----------+ +------------+
49+
```
50+
51+
### Puppetfile Parser
52+
53+
The parser converts the content of a Puppetfile into a document model (`PuppetfileResolver::Puppetfile`).
54+
55+
Currently only one Parser is available, `R10KEval`, which uses the same parsing logic as the [R10K Parser](https://github.com/puppetlabs/r10k/blob/master/lib/r10k/puppetfile.rb). In the future other parsers may be added, such as a [Ruby AST based parser](https://github.com/puppetlabs/r10k/pull/885).
56+
57+
### Puppetfile Document Validation
58+
59+
Even though a Puppetfile can be parsed, doesn't mean it's valid. For example, defining a module twice.
60+
61+
### Puppetfile Resolution
62+
63+
Given a Puppetfile document model, the library can attempt to recursively resolve all of the modules and their dependencies. The resolver be default will not be strict, that is, missing dependencies will not throw an error, and will attempt to also be resolved. When in strict mode, any missing dependencies will throw errors.
64+
65+
### Module Searchers
66+
67+
The Puppetfile resolution needs information about all of the available modules and versions, and does this through calling various Specification Searchers. Currently Puppet Forge, Github and Local FileSystem searchers are implemented. Additional searchers could be added, for example GitLab or SVN.
68+
69+
The result is a dependency graph listing all of the modules, dependencies and version information.
70+
71+
### Resolution validation
72+
73+
Even though a Puppetfile can be resolved, doesn't mean it is valid. For example, missing module dependencies are not considered valid.
74+
75+
### Dependency Graph
76+
77+
The resolver uses the [Molinillo](https://github.com/CocoaPods/Molinillo) ruby gem for dependency resolution. Molinillo is used in Bundler, among other gems, so it's well used and maintained project.
78+
79+
### Example workflow
80+
81+
1. Load the contents of a Puppetfile from disk
82+
83+
2. Parse the Puppetfile into a document model
84+
85+
3. Verify that the document model is valid
86+
87+
4. Create a resolver object with the document model, and the required Puppet version (optional)
88+
89+
5. Start the resolution
90+
91+
6. Validate the resolution against the document model
92+
93+
### Example usage
94+
95+
``` ruby
96+
puppetfile_path = '/path/to/Puppetfile'
97+
98+
# Parse the Puppetfile into an object model
99+
content = File.open(puppetfile_path, 'rb') { |f| f.read }
100+
require 'puppetfile-resolver/puppetfile/parser/r10k_eval'
101+
puppetfile = ::PuppetfileResolver::Puppetfile::Parser::R10KEval.parse(content)
102+
103+
# Make sure the Puppetfile is valid
104+
unless puppetfile.valid?
105+
puts 'Puppetfile is not valid'
106+
puppetfile.validation_errors.each { |err| puts err }
107+
exit 1
108+
end
109+
110+
# Create the resolver
111+
# - Use the document we just parsed (puppetfile)
112+
# - Don't restrict by Puppet version (nil)
113+
resolver = PuppetfileResolver::Resolver.new(puppetfile, nil)
114+
115+
# Configure the resolver
116+
cache = nil # Use the default inmemory cache
117+
ui = nil # Don't output any information
118+
module_paths = [] # List of paths to search for modules on the local filesystem
119+
allow_missing_modules = true # Allow missing dependencies to be resolved
120+
opts = { cache: cache, ui: ui, module_paths: module_paths, allow_missing_modules: allow_missing_modules }
121+
122+
# Resolve
123+
result = resolver.resolve(opts)
124+
125+
# Output resolution validation errors
126+
result.validation_errors.each { |err| puts "Resolution Validation Error: #{err}\n"}
127+
```
128+
129+
## Known issues
130+
131+
- The Forge module searcher will only use the internet facing forge ([https://forge.puppet.com](https://forge.puppet.com/)). Self-hosted forges are not supported
132+
133+
- The Git module searcher will only search public Github based modules. Private repositories or other VCS systems are not supported

lib/puppetfile-resolver.rb

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
require 'puppetfile-resolver/version'
4+
require 'puppetfile-resolver/puppetfile'
5+
require 'puppetfile-resolver/util'
6+
require 'puppetfile-resolver/models'
7+
require 'puppetfile-resolver/resolver'

lib/puppetfile-resolver/cache/base.rb

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
module PuppetfileResolver
4+
module Cache
5+
class Base
6+
# TODO: Need to make sure I cache everything!
7+
# External calls are expensive
8+
# def xxx
9+
# @inmemory
10+
# end
11+
12+
def initialize(*_)
13+
@inmemory = {}
14+
end
15+
16+
def exist?(name)
17+
@inmemory.key?(name)
18+
end
19+
20+
def load(name)
21+
@inmemory[name]
22+
end
23+
24+
def save(name, value, persist = false)
25+
@inmemory[name] = value
26+
persist(name, value) if persist
27+
end
28+
29+
def persist(_name, content_string)
30+
raise 'Can only persist String data types' unless content_string.is_a?(String)
31+
end
32+
end
33+
end
34+
end

0 commit comments

Comments
 (0)