From e04e71f2c558fcdcd29290bd0a1999a159eb011f Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Fri, 24 Jul 2026 16:39:23 +0000 Subject: [PATCH 1/2] Document the provider and validate REFERENCE.md in CI (2.0.1) - Inline puppet-strings/YARD documentation for the gpasswd provider: an expanded desc covering membership semantics plus method-level docs (parameters, returns, version-compat rationale) - Commit the generated REFERENCE.md and add a reference-docs CI job running rake validate:strings so it can never go stale - Note: the openvox-strings markdown generator parses providers but has no template to render them, so REFERENCE.md is currently header-only for this provider-only module; validate:strings will flag the file for regeneration as soon as that is fixed upstream Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XCnDsYaJDLiP8z8tafz9Tp --- .github/workflows/pr_tests.yml | 12 +++++ CHANGELOG | 7 +++ REFERENCE.md | 6 +++ lib/puppet/provider/group/gpasswd.rb | 67 ++++++++++++++++++++++++++-- metadata.json | 2 +- 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 REFERENCE.md diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 4efa736..445aa9b 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -88,6 +88,18 @@ jobs: - name: 'Test-build the Puppet module' run: 'bundle exec rake pupmod:build' + reference-docs: + name: 'REFERENCE.md is up to date' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: 'Install Ruby 3.4' + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4.9 + bundler-cache: true + - run: 'bundle exec rake validate:strings' + spec-tests: name: 'Puppet Spec' needs: [puppet-syntax] diff --git a/CHANGELOG b/CHANGELOG index 3972c30..92002b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +* Fri Jul 24 2026 Steven Pritchard - 2.0.1 +- Document the gpasswd provider inline (puppet-strings/YARD comments) +- Add REFERENCE.md and a CI job that keeps it validated + (`rake validate:strings`); note that the current openvox-strings + markdown generator does not yet render providers, so the file gains + real content once that is fixed upstream + * Thu Jul 16 2026 Steven Pritchard - 2.0.0 - Migrate to the OpenVox test stack (openvox gem, voxpupuli-test, simp-rake-helpers 6.x, simp-beaker-helpers 3.x) and add a Ruby 4.0 / diff --git a/REFERENCE.md b/REFERENCE.md new file mode 100644 index 0000000..540f19b --- /dev/null +++ b/REFERENCE.md @@ -0,0 +1,6 @@ +# Reference + + + +## Table of Contents + diff --git a/lib/puppet/provider/group/gpasswd.rb b/lib/puppet/provider/group/gpasswd.rb index 157e991..1ad113b 100644 --- a/lib/puppet/provider/group/gpasswd.rb +++ b/lib/puppet/provider/group/gpasswd.rb @@ -1,11 +1,29 @@ require 'puppet/provider/group/groupadd' +# @summary Local group membership management via `gpasswd` Puppet::Type.type(:group).provide :gpasswd, parent: Puppet::Type::Group::ProviderGroupadd do require 'shellwords' desc <<~EOM - Group management via `gpasswd`. This allows for local group - management when the users exist in a remote system. + Group management via `gpasswd`. + + Extends the standard `groupadd` provider with support for the + `manages_members` feature so that the members of a local group can be + managed with the native `group` type, even when the users themselves are + defined in a remote system such as LDAP: + + group { 'test': + members => ['foo', 'bar', 'baz'], + } + + The group's `auth_membership` parameter selects exclusive membership + (`gpasswd -M`, membership matches the catalog exactly) or additive + membership (`gpasswd -a` per user, existing members are preserved). + + Members that the system cannot resolve (via `getpwnam`/`getpwuid`) are + skipped with a warning instead of failing the resource, and errors from + individual membership changes are downgraded to warnings so that one bad + member does not abort management of the rest of the group. EOM commands addmember: 'gpasswd', @@ -15,11 +33,22 @@ has_feature :libuser if Puppet.features.libuser? has_feature :system_groups unless ['HP-UX', 'Solaris'].include? Facter.value(:operatingsystem) + # Whether the running Puppet represents the `members` property as a + # `Puppet::Property::List` (comma-joined string) rather than a plain Array + # + # @return [Boolean] def is_new_format? # rubocop:disable Style/PredicatePrefix defined?(Puppet::Property::List) && @resource.parameter('members').class.ancestors.include?(Puppet::Property::List) end + # Create the group and add any initial members with `gpasswd -a` + # + # The parent provider's group creation command and the per-member `gpasswd` + # commands are executed here (see `mod_group`) rather than returned, because + # the Nameservice layer can only execute a single command. + # + # @return [String] a no-op command for the Nameservice layer to execute def addcmd # This pulls in the main group add command should the group need # to be added from scratch. @@ -38,9 +67,15 @@ def addcmd '/bin/true' end + # Build the command that modifies a group property + # # This is a repeat from puppet/provider/nameservice/objectadd. # The self.class.name matches are hard coded so cannot be easily # overridden. + # + # @param param [Symbol] the property being modified + # @param value [String] the new value + # @return [Array] the command to execute def modifycmd(param, value) cmd_type = (param.to_s =~ %r{password_.+_age}) ? :password : :modify cmd = [command(cmd_type)] @@ -62,6 +97,14 @@ def modifycmd(param, value) cmd end + # Current group members, as read from the system group database + # + # When `auth_membership` is false and all desired members are already + # present, the desired value is returned instead so that the property is + # seen as in sync. + # + # @return [Array, String, nil] the membership in the format the + # running Puppet's `members` property expects (see `is_new_format?`) def members members_to_set = @resource.parameter('members').shouldorig @@ -91,6 +134,11 @@ def members retval end + # Whether the current membership matches the desired membership + # + # @param is [Array] the current members + # @param should [Array, String] the desired members + # @return [Boolean] def members_insync?(is, should) # We need to remove any user that the system doesn't recognize, otherwise # the add and/or remove commands will fail. @@ -115,6 +163,14 @@ def members_insync?(is, should) Array(is).sort.uniq == sorted_should end + # Set the group membership with `gpasswd` + # + # Uses `gpasswd -M` (exclusive) when `auth_membership` is true and + # per-member `gpasswd -a` (additive) otherwise. + # + # @param to_set [Array, String] the desired members, in the format + # the running Puppet's `members` property provides (see `is_new_format?`) + # @return [void] def members=(to_set) cmd = [] @@ -140,8 +196,8 @@ def members=(to_set) private - # This define takes an array of commands to run and executes them in - # order to modify the group memberships on the system. + # Execute a list of group modification commands in order + # # A useful warning message is output if there is an issue modifying # the group but all members that can be added are added. This is an # attempt to do the "right thing" without actually breaking a run @@ -150,6 +206,9 @@ def members=(to_set) # # The run of the type *will* succeed in all cases and present warnings to the # user. + # + # @param cmds [Array] shell commands to execute + # @return [void] def mod_group(cmds) cmds.each do |run_cmd| output = execute(run_cmd, custom_environment: @custom_environment, failonfail: false, combine: true) diff --git a/metadata.json b/metadata.json index d1c2a79..16e2a65 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "simp-gpasswd", - "version": "2.0.0", + "version": "2.0.1", "author": "SIMP Team", "summary": "Adds support for :manages_members to the Linux group native type", "license": "Apache-2.0", From 591d235fda5a1e22bc2904cfbda500ce31d0f27b Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Fri, 24 Jul 2026 19:22:27 +0000 Subject: [PATCH 2/2] Regenerate REFERENCE.md with provider rendering Temporarily source openvox-strings from the add-provider-markdown branch on silug/openvox-strings, which teaches the markdown generator to render providers; REFERENCE.md now documents the gpasswd provider (description, membership semantics, and commands). Revert to the released gem once the change is merged and published upstream. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XCnDsYaJDLiP8z8tafz9Tp --- CHANGELOG | 6 +++--- Gemfile | 3 ++- REFERENCE.md | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 92002b2..38e792e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,9 @@ * Fri Jul 24 2026 Steven Pritchard - 2.0.1 - Document the gpasswd provider inline (puppet-strings/YARD comments) - Add REFERENCE.md and a CI job that keeps it validated - (`rake validate:strings`); note that the current openvox-strings - markdown generator does not yet render providers, so the file gains - real content once that is fixed upstream + (`rake validate:strings`); openvox-strings is temporarily sourced + from a fork branch that adds provider rendering to the markdown + generator, pending its release upstream * Thu Jul 16 2026 Steven Pritchard - 2.0.0 - Migrate to the OpenVox test stack (openvox gem, voxpupuli-test, diff --git a/Gemfile b/Gemfile index a6275a3..0b81bbb 100644 --- a/Gemfile +++ b/Gemfile @@ -23,7 +23,8 @@ group :test do openvox_version = ENV.fetch('OPENVOX_VERSION', puppet_version) gem 'hiera-puppet-helper' gem 'openvox', openvox_version - gem 'openvox-strings' + # TODO: revert to the released gem once provider rendering is merged upstream + gem 'openvox-strings', git: 'https://github.com/silug/openvox-strings', branch: 'add-provider-markdown' gem 'rake' gem 'rspec' gem 'rspec-puppet' diff --git a/REFERENCE.md b/REFERENCE.md index 540f19b..5f5b680 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -4,3 +4,37 @@ ## Table of Contents +### Providers + +* [`gpasswd`](#provider_group_gpasswd): Group management via `gpasswd`. Extends the standard `groupadd` provider with support for the `manages_members` feature so that the members + +## Providers + +### `gpasswd` + +Provider for the `group` resource type. + +Group management via `gpasswd`. + +Extends the standard `groupadd` provider with support for the +`manages_members` feature so that the members of a local group can be +managed with the native `group` type, even when the users themselves are +defined in a remote system such as LDAP: + + group { 'test': + members => ['foo', 'bar', 'baz'], + } + +The group's `auth_membership` parameter selects exclusive membership +(`gpasswd -M`, membership matches the catalog exactly) or additive +membership (`gpasswd -a` per user, existing members are preserved). + +Members that the system cannot resolve (via `getpwnam`/`getpwuid`) are +skipped with a warning instead of failing the resource, and errors from +individual membership changes are downgraded to warnings so that one bad +member does not abort management of the rest of the group. + +#### Commands + +* `addmember`: `gpasswd` +* `modmember`: `gpasswd`