Skip to content

Commit 3343f91

Browse files
committed
(PUP-12057) Generate unified resource types page
This commit runs `ruby get_typedocs.rb` to extract the resource type information and render the unified page for all resource types. This code should be converted to use the puppet strings data that we're using to generate the type overview page.
1 parent cc89237 commit 3343f91

File tree

4 files changed

+443
-2
lines changed

4 files changed

+443
-2
lines changed

rakelib/generate_references.rake

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ MAN_OVERVIEW_MD = File.join(MANDIR, "overview.md")
1818
MAN_ERB = File.join(__dir__, 'references/man.erb')
1919
TYPES_OVERVIEW_ERB = File.join(__dir__, 'references/types/overview.erb')
2020
TYPES_OVERVIEW_MD = File.join(TYPES_DIR, 'overview.md')
21+
UNIFIED_TYPE_ERB = File.join(__dir__, 'references/unified_type.erb')
22+
UNIFIED_TYPE_MD = File.join(OUTPUT_DIR, 'type.md')
2123

2224
def render_erb(erb_file, variables)
2325
# Create a binding so only the variables we specify will be visible
@@ -46,6 +48,36 @@ def generate_reference(reference, erb, body, output)
4648
puts "Generated #{output}"
4749
end
4850

51+
# Render type information for the specified resource type
52+
# Based on https://github.com/puppetlabs/puppet-docs/blob/1a13be3fc6981baa8a96ff832ab090abc986830e/lib/puppet_references/puppet/type.rb#L87-L112
53+
def render_resource_type(name, this_type)
54+
sorted_attribute_list = this_type['attributes'].keys.sort {|a,b|
55+
# Float namevar(s) to the top and ensure after
56+
# followed by the others in sort order
57+
if this_type['attributes'][a]['namevar']
58+
-1
59+
elsif this_type['attributes'][b]['namevar']
60+
1
61+
elsif a == 'ensure'
62+
-1
63+
elsif b == 'ensure'
64+
1
65+
else
66+
a <=> b
67+
end
68+
}
69+
70+
variables = {
71+
name: name,
72+
this_type: this_type,
73+
sorted_attribute_list: sorted_attribute_list,
74+
sorted_feature_list: this_type['features'].keys.sort,
75+
longest_attribute_name: sorted_attribute_list.collect{|attr| attr.length}.max
76+
}
77+
erb = File.join(__dir__, 'references/types/type.erb')
78+
render_erb(erb, variables)
79+
end
80+
4981
# Based on https://github.com/puppetlabs/puppet-docs/blob/1a13be3fc6981baa8a96ff832ab090abc986830e/lib/puppet_references/puppet/type_strings.rb#L19-L99
5082
def extract_resource_types(strings_data)
5183
strings_data['resource_types'].reduce(Hash.new) do |memo, type|
@@ -131,6 +163,19 @@ def extract_resource_types(strings_data)
131163
end
132164
end
133165

166+
# Extract type documentation from the current version of puppet. Based on
167+
# https://github.com/puppetlabs/puppet-docs/blob/1a13be3fc6981baa8a96ff832ab090abc986830e/lib/puppet_references/puppet/type.rb#L52
168+
#
169+
# REMIND This is kind of convoluted and means we're using two completely different
170+
# code paths to generate the overview and unified page of types.
171+
def unified_page_resource_types
172+
type_json = %x{ruby #{File.join(__dir__, 'references/get_typedocs.rb')}}
173+
type_data = JSON.load(type_json)
174+
type_data.keys.sort.map do |name|
175+
render_resource_type(name, type_data[name])
176+
end
177+
end
178+
134179
namespace :references do
135180
desc "Generate configuration reference"
136181
task :configuration do
@@ -299,6 +344,7 @@ namespace :references do
299344
end
300345

301346
sha = %x{git rev-parse HEAD}.chomp
347+
now = Time.now
302348

303349
# Based on https://github.com/puppetlabs/puppet-docs/blob/1a13be3fc6981baa8a96ff832ab090abc986830e/lib/puppet_references/puppet/strings.rb#L25-L26
304350
Tempfile.create do |tmpfile|
@@ -316,16 +362,29 @@ namespace :references do
316362
end
317363

318364
variables = {
319-
sha: sha,
320-
now: Time.now,
321365
title: 'Resource types overview',
366+
sha: sha,
367+
now: now,
322368
types: types
323369
}
324370

325371
# Render overview page
326372
content = render_erb(TYPES_OVERVIEW_ERB, variables)
327373
File.write(TYPES_OVERVIEW_MD, content)
328374
puts "Generated #{TYPES_OVERVIEW_MD}"
375+
376+
# Based on https://github.com/puppetlabs/puppet-docs/blob/1a13be3fc6981baa8a96ff832ab090abc986830e/lib/puppet_references/puppet/type.rb#L55-L70
377+
# unified page of types
378+
variables = {
379+
title: 'Resource Type Reference (Single-Page)',
380+
sha: sha,
381+
now: now,
382+
types: unified_page_resource_types
383+
}
384+
385+
content = render_erb(UNIFIED_TYPE_ERB, variables)
386+
File.write(UNIFIED_TYPE_MD, content)
387+
puts "Generated #{UNIFIED_TYPE_MD}"
329388
end
330389
end
331390
end

rakelib/references/get_typedocs.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# This script will print the Puppet type docs to stdout in JSON format.
2+
3+
# There are some subtleties that make this a pain to run. Basically: Even if you
4+
# 'require' a specific copy of the Puppet code, the autoloader will grab bits
5+
# and pieces of Puppet code from other copies of Puppet scattered about the Ruby
6+
# load path. This causes a mixture of docs from different versions: although I
7+
# think the version you require will usually win for things that exist in both
8+
# versions, providers or attributes that only exist in one version will leak
9+
# through and you'll get an amalgamation.
10+
11+
# So the only safe thing to do is run this in a completely separate process, and
12+
# ruthlessly control the Ruby load path. We expect that when you're executing
13+
# this code, your $RUBYLIB contains the version of Puppet you want to load, and
14+
# there are no other versions of Puppet available as gems, installed via system
15+
# packages, etc. etc. etc.
16+
17+
require 'json'
18+
require 'puppet'
19+
require 'puppet/util/docs'
20+
extend Puppet::Util::Docs
21+
# We use scrub().
22+
23+
24+
# The schema of the typedocs object:
25+
26+
# { :name_of_type => {
27+
# :description => 'Markdown fragment: description of type',
28+
# :features => { :feature_name => 'feature description', ... }
29+
# # If there are no features, the value of :features will be an empty hash.
30+
# :providers => { # If there are no providers, the value of :providers will be an empty hash.
31+
# :name_of_provider => {
32+
# :description => 'Markdown fragment: docs for this provider',
33+
# :features => [:feature_name, :other_feature, ...]
34+
# # If provider has no features, the value of :features will be an empty array.
35+
# },
36+
# ...etc...
37+
# }
38+
# :attributes => { # Puppet dictates that there will ALWAYS be at least one attribute.
39+
# :name_of_attribute => {
40+
# :description => 'Markdown fragment: docs for this attribute',
41+
# :kind => (:property || :parameter),
42+
# :namevar => (true || false), # always false if :kind => :property
43+
# },
44+
# ...etc...
45+
# },
46+
# },
47+
# ...etc...
48+
# }
49+
typedocs = {}
50+
51+
Puppet::Type.loadall
52+
53+
Puppet::Type.eachtype { |type|
54+
# List of types to ignore:
55+
next if type.name == :puppet
56+
next if type.name == :component
57+
next if type.name == :whit
58+
59+
# Initialize the documentation object for this type
60+
docobject = {
61+
:description => scrub(type.doc),
62+
:attributes => {}
63+
}
64+
65+
# Handle features:
66+
# inject will return empty hash if type.features is empty.
67+
docobject[:features] = type.features.inject( {} ) { |allfeatures, name|
68+
allfeatures[name] = scrub( type.provider_feature(name).docs )
69+
allfeatures
70+
}
71+
72+
# Handle providers:
73+
# inject will return empty hash if type.providers is empty.
74+
docobject[:providers] = type.providers.inject( {} ) { |allproviders, name|
75+
allproviders[name] = {
76+
:description => scrub( type.provider(name).doc ),
77+
:features => type.provider(name).features
78+
}
79+
allproviders
80+
}
81+
82+
# Override several features missing due to bug #18426:
83+
if type.name == :user
84+
docobject[:providers][:useradd][:features] << :manages_passwords << :manages_password_age << :libuser
85+
if docobject[:providers][:openbsd]
86+
docobject[:providers][:openbsd][:features] << :manages_passwords << :manages_loginclass
87+
end
88+
end
89+
if type.name == :group
90+
docobject[:providers][:groupadd][:features] << :libuser
91+
end
92+
93+
94+
# Handle properties:
95+
docobject[:attributes].merge!(
96+
type.validproperties.inject( {} ) { |allproperties, name|
97+
property = type.propertybyname(name)
98+
raise "Could not retrieve property #{propertyname} on type #{type.name}" unless property
99+
description = property.doc
100+
$stderr.puts "No docs for property #{name} of #{type.name}" unless description and !description.empty?
101+
102+
allproperties[name] = {
103+
:description => scrub(description),
104+
:kind => :property,
105+
:namevar => false # Properties can't be namevars.
106+
}
107+
allproperties
108+
}
109+
)
110+
111+
# Handle parameters:
112+
docobject[:attributes].merge!(
113+
type.parameters.inject( {} ) { |allparameters, name|
114+
description = type.paramdoc(name)
115+
$stderr.puts "No docs for parameter #{name} of #{type.name}" unless description and !description.empty?
116+
117+
# Strip off the too-huge provider list. The question of what to do about
118+
# providers is a decision for the formatter, not the fragment collector.
119+
description = description.split('Available providers are')[0] if name == :provider
120+
121+
allparameters[name] = {
122+
:description => scrub(description),
123+
:kind => :parameter,
124+
:namevar => type.key_attributes.include?(name) # returns a boolean
125+
}
126+
allparameters
127+
}
128+
)
129+
130+
# Finally:
131+
typedocs[type.name] = docobject
132+
}
133+
134+
print JSON.dump(typedocs)

rakelib/references/types/type.erb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
## <%= name %>
2+
3+
* [Attributes](#<%= name %>-attributes)
4+
<% if !this_type['providers'].empty? -%>
5+
* [Providers](#<%= name %>-providers)
6+
<% end -%>
7+
<% if !this_type['features'].empty? -%>
8+
* [Provider Features](#<%= name %>-provider-features)
9+
<% end -%>
10+
11+
### Description {#<%= name %>-description}
12+
13+
<%= this_type['description'] %>
14+
15+
### Attributes {#<%= name %>-attributes}
16+
17+
<pre><code><%= name %> { 'resource title':
18+
<% sorted_attribute_list.each do |attribute_name| -%>
19+
<a href="#<%= name %>-attribute-<%= attribute_name %>"><%= attribute_name %></a><%= ' ' * (longest_attribute_name - attribute_name.length) %> =&gt; <em># <% if this_type['attributes'][attribute_name]['namevar'] %><strong>(namevar)</strong> <% end %><%= this_type['attributes'][attribute_name]['description'][0,49].gsub("\n", ' ').gsub('<', '&lt;').sub(/\W? \S+$/, '...') %></em>
20+
<% end -%>
21+
# ...plus any applicable <a href="https://puppet.com/docs/puppet/latest/metaparameter.html">metaparameters</a>.
22+
}</code></pre>
23+
24+
<% sorted_attribute_list.each do |attribute_name| -%>
25+
26+
#### <%= attribute_name %> {#<%= name %>-attribute-<%= attribute_name %>}
27+
28+
<% if this_type['attributes'][attribute_name]['namevar'] -%>
29+
<% if attribute_name != 'provider' %>_(**Namevar:** If omitted, this attribute's value defaults to the resource's title.)_<%= "\n\n" -%>
30+
<% elsif attribute_name == 'provider' %>_(**Secondary namevar:** This resource type allows you to manage multiple resources with the same name as long as their providers are different.)_<%= "\n\n" -%>
31+
<% end -%>
32+
<% end -%>
33+
<% if this_type['attributes'][attribute_name]['kind'] == 'property' %>_(**Property:** This attribute represents concrete state on the target system.)_<%= "\n\n" %><% end -%>
34+
<%= this_type['attributes'][attribute_name]['description'] %>
35+
<% if this_type['attributes'][attribute_name]['required_features'] -%>
36+
37+
Requires features <%= this_type['attributes'][attribute_name]['required_features'] %>.
38+
<% end -%>
39+
40+
<% if attribute_name == 'provider' and !this_type['providers'].empty? -%>Available providers are:<%= "\n\n" %><%= this_type['providers'].keys.sort.collect {|prov| "* [`#{prov}`](##{name}-provider-#{prov})"}.sort.join("\n") %><%= "\n\n" %><% end -%>
41+
([↑ Back to <%= name %> attributes](#<%= name %>-attributes))
42+
43+
<% end # of attribute details
44+
-%>
45+
46+
<% if !this_type['providers'].empty? -%>
47+
### Providers {#<%= name %>-providers}
48+
49+
<% end -%>
50+
<% this_type['providers'].keys.sort.each do |provider_name| -%>
51+
#### <%= provider_name %> {#<%= name %>-provider-<%= provider_name %>}
52+
53+
<%= this_type['providers'][provider_name]['description'] %>
54+
55+
<% end -%>
56+
<% if !this_type['features'].empty? -%>
57+
### Provider Features {#<%= name %>-provider-features}
58+
59+
Available features:
60+
61+
<% sorted_feature_list.each do |feature| -%>
62+
* `<%= feature %>` --- <%= this_type['features'][feature].gsub("\n", ' ') %>
63+
<% end -%>
64+
65+
<% if !this_type['providers'].empty? -%>
66+
Provider support:
67+
68+
<%- this_type['providers'].keys.sort.each do |provider_name| -%>
69+
* **<%= provider_name %>** - <% if !this_type['providers'][provider_name]['features'].empty? -%>
70+
_<%=this_type['providers'][provider_name]['features'].join(', ').gsub('_', ' ') %>_
71+
<% else %>No supported Provider features
72+
<% end %><%- end -%>
73+
74+
<% end # provider support table
75+
-%>
76+
<% end # features section
77+
-%>
78+

0 commit comments

Comments
 (0)