Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dnf module management - Fix #310 #320

Merged
merged 23 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
305886b
Add spec tests for dnf_module_stream custom resource
EmersonPrado Aug 12, 2023
a774e89
Add dnf_module_stream custom resource type
EmersonPrado Aug 12, 2023
42dab51
Add dnf_module_stream custom resource provider getter/setter signatures
EmersonPrado Aug 12, 2023
4429b2a
Distinguish title and module name in dnf_module_stream custom resource
EmersonPrado Aug 12, 2023
ea9c60d
Add dnf command in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
68d1b04
Query module stream state in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
fe4d08e
Detect dnf output format in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
2486a5f
Get state from dnf output in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
70d26b6
Implement stream: absent in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
9590460
Implement stream: default in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
d3ce8b9
Implement stream: present in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
3fb16a9
Implement stream as string in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
181f933
Choose enable or switch-to in dnf_module_stream custom resource provider
EmersonPrado Aug 12, 2023
c2401e4
Update REFERENCE.md with dnf_module_stream custom resource
EmersonPrado Aug 12, 2023
495d555
Add in README dnf_module_stream custom resource usage
EmersonPrado Aug 22, 2023
18e8fff
Add in README dnf_module_stream "switch-to" warning
EmersonPrado Aug 22, 2023
b1a341a
Add in README dnf_module_stream reasoning
EmersonPrado Aug 22, 2023
125a681
Fix "absent" value in dnf_module_stream type stream property docs
EmersonPrado Aug 22, 2023
18563fc
Update REFERENCE.md with fixed "absent" value
EmersonPrado Aug 23, 2023
8925d29
Skip yum::plugin::post_transaction_actions unit tests due to OracleLi…
EmersonPrado Aug 24, 2023
b857bef
Uninstall vim-enhanced for yum::post_transaction_action acceptance tests
EmersonPrado Aug 24, 2023
f726081
Skip yum::plugin::post_transaction_actions acceptance, not unit tests
EmersonPrado Aug 24, 2023
49e8b42
Remove "pending" acceptance test for Oracle 7
EmersonPrado Apr 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,56 @@ yum::install { 'package-name':

Please note that resource name must be same as installed package name.

### Manage DNF modules streams

> When changing from one enabled stream to another one, the provider runs `dnf module switch-to <Stream>`, which replaces all installed profiles from the DNF module. Bear the consequences in mind.

Enable default stream

```puppet
dnf_module_stream { '<Module>':
stream => default,
}
```

Keep current enabled stream - if there isn't, enable default one

```puppet
dnf_module_stream { '<Module>':
stream => present,
}
```

Enable a specific stream

```puppet
dnf_module_stream { '<Module>':
stream => <Stream name>,
}
```

Disable stream (reset module)

```puppet
dnf_module_stream { '<Module>':
stream => absent,
}
```

#### `dnf_module_stream` resource versus `dnfmodule` provider

[DNF modules](https://dnf.readthedocs.io/en/latest/modularity.html) is a feature from `yum` successor, `dnf`, which allows easier and more robust selections of software versions and collections.

As of Aug 22, 2023, [core Puppet `package` resource `dnfmodule` provider](https://www.puppet.com/docs/puppet/8/types/package.html#package-provider-dnfmodule) has some support for managing streams and profiles, but it has some issues:

1. Setting stream is mandatory when (un)installing profiles - No way of just keeping currently enabled stream
1. It only supports installing a single profile, despite the fact `dnf` supports multi-profile installations and there are use cases for that
1. Managing two things - streams setting and profile (un)installation - in the same resource invocation is inherently messy

One can fix 1 and 2, and add good docs to deal with 3. A compelling reason not to keep 1 and 3 is that a stream is a setting, not something one (un)installs. This makes it unsuitable for the `package` resource which, in principle, should only (un)install stuff.

So, while one fix 2, this custom resource aims to fully and better replace `dnfmodule` provider stream support.

### Puppet tasks

The module has a puppet task that allows to run `yum update` or `yum upgrade`.
Expand Down
75 changes: 75 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
* [`yum::post_transaction_action`](#yum--post_transaction_action): Creates post transaction configuratons for dnf or yum.
* [`yum::versionlock`](#yum--versionlock): Locks package from updates.

### Resource types

* [`dnf_module_stream`](#dnf_module_stream): Manage DNF module streams

### Functions

* [`yum::bool2num_hash_recursive`](#yum--bool2num_hash_recursive): This functions converts the Boolean values of a Hash to Integers, either '0' or '1'. It does this recursively, decending as far as the langu
Expand Down Expand Up @@ -825,6 +829,77 @@ Epoch of the package if CentOS 8 mechanism is used.

Default value: `0`

## Resource types

### <a name="dnf_module_stream"></a>`dnf_module_stream`

This type allows Puppet to enable/disable streams via DNF modules

#### Examples

##### Enable MariaDB default stream

```puppet
dnf_module_stream { 'mariadb':
stream => default,
}
```

##### Enable MariaDB 10.5 stream

```puppet
dnf_module_stream { 'mariadb':
stream => '10.5',
}
```

##### Disable MariaDB streams

```puppet
dnf_module_stream { 'mariadb':
stream => absent,
}
```

#### Properties

The following properties are available in the `dnf_module_stream` type.

##### `stream`

Valid values: `present`, `default`, `absent`, `%r{.+}`

Module stream that should be enabled
String - Specify stream
present - Keep current enabled stream if any, otherwise enable default one
default - Enable default stream
absent - No stream (resets module)

#### Parameters

The following parameters are available in the `dnf_module_stream` type.

* [`module`](#-dnf_module_stream--module)
* [`provider`](#-dnf_module_stream--provider)
* [`title`](#-dnf_module_stream--title)

##### <a name="-dnf_module_stream--module"></a>`module`

Valid values: `%r{.+}`

DNF module to be managed

##### <a name="-dnf_module_stream--provider"></a>`provider`

The specific backend to use for this `dnf_module_stream` resource. You will seldom need to specify this --- Puppet will
usually discover the appropriate provider for your platform.

##### <a name="-dnf_module_stream--title"></a>`title`

Valid values: `%r{.+}`

Resource title

## Functions

### <a name="yum--bool2num_hash_recursive"></a>`yum::bool2num_hash_recursive`
Expand Down
100 changes: 100 additions & 0 deletions lib/puppet/provider/dnf_module_stream/dnf_module_stream.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

Puppet::Type.type(:dnf_module_stream).provide(:dnf_module_stream) do
desc 'Unique provider'

confine package_provider: 'dnf'

commands dnf: 'dnf'

# Converts plain output from 'dnf module list <Module>' to an array formatted as:
# {
# default_stream: "<Default stream> (if there's one)",
# enabled_stream: "<Enabled stream> (if there's one)",
# available_streams: ["<Stream>", "<Stream>", ...,]
# }
def dnf_output_2_hash(dnf_output)
module_hash = { available_streams: [] }
dnf_output.lines.each do |line|
line.chomp!
break if line.empty?

# @stream_start and @stream_length: chunk of dnf output line with stream info
# Determined in elsif block below from dnf output header
if !@stream_start.nil?
# Stream string is '<Stream>', '<Stream> [d][e]', or the like
stream_string = line[@stream_start, @stream_length].rstrip
stream = stream_string.split[0]
module_hash[:default_stream] = stream if stream_string.include?('[d]')
module_hash[:enabled_stream] = stream if stream_string.include?('[e]')
module_hash[:available_streams] << stream
elsif line.split[0] == 'Name'
# 'dnf module list' output header is 'Name<Spaces>Stream<Spaces>Profiles<Spaces>...'
# Each field has same position of data that follows
@stream_start = line[%r{Name\s+}].length
@stream_length = line[%r{Stream\s+}].length
end
end
module_hash
end

# Gets module default, enabled and available streams
# Output formatted by function dnf_output_2_hash
def streams_state(module_name)
# This function can be called multiple times in the same resource call
return unless @streams_current_state.nil?

dnf_output = dnf('-q', 'module', 'list', module_name)
rescue Puppet::ExecutionFailure
# Assumes any execution error happens because module doesn't exist
raise ArgumentError, "Module \"#{module_name}\" not found"
else
@streams_current_state = dnf_output_2_hash(dnf_output)
end

def disable_stream(module_name)
dnf('-y', 'module', 'reset', module_name)
end

def enable_stream(module_name, target_stream)
action = @streams_current_state.key?(:enabled_stream) ? 'switch-to' : 'enable'
dnf('-y', 'module', action, "#{module_name}:#{target_stream}")
end

def stream
streams_state(resource[:module])
case resource[:stream]
when :absent
# Act if any stream is enabled
@streams_current_state.key?(:enabled_stream) ? @streams_current_state[:enabled_stream] : :absent
when :default
# Act if default stream isn't enabled
# Specified stream = :default requires an existing default stream
raise ArgumentError, "No default stream to enable in module \"#{resource[:module]}\"" unless
@streams_current_state.key?(:default_stream)

@streams_current_state[:enabled_stream] == @streams_current_state[:default_stream] ? :default : @streams_current_state[:enabled_stream]
when :present
# Act if no stream is enabled
# Specified stream = :default requires an existing default or enabled stream
raise ArgumentError, "No default stream to enable in module \"#{resource[:module]}\"" unless
@streams_current_state.key?(:default_stream) || @streams_current_state.key?(:enabled_stream)

@streams_current_state.key?(:enabled_stream) ? :present : :absent
else
# Act if specified stream isn't enabled
@streams_current_state[:enabled_stream]
end
end

def stream=(target_stream)
case target_stream
when :absent
disable_stream(resource[:module])
when :default, :present
enable_stream(resource[:module], @streams_current_state[:default_stream])
else
enable_stream(resource[:module], target_stream)
end
end
end
46 changes: 46 additions & 0 deletions lib/puppet/type/dnf_module_stream.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

Puppet::Type.newtype(:dnf_module_stream) do
@doc = <<-TYPE_DOC
@summary Manage DNF module streams
@example Enable MariaDB default stream
dnf_module_stream { 'mariadb':
stream => default,
}
@example Enable MariaDB 10.5 stream
dnf_module_stream { 'mariadb':
stream => '10.5',
}
@example Disable MariaDB streams
dnf_module_stream { 'mariadb':
stream => absent,
}
@param module
Module to be managed - Defaults to title
@param stream
Module stream to be enabled

This type allows Puppet to enable/disable streams via DNF modules
TYPE_DOC

newparam(:title, namevar: true) do
desc 'Resource title'
newvalues(%r{.+})
end

newparam(:module) do
desc 'DNF module to be managed'
newvalues(%r{.+})
end

newproperty(:stream) do
desc <<-EOS
Module stream that should be enabled
String - Specify stream
present - Keep current enabled stream if any, otherwise enable default one
default - Enable default stream
absent - No stream (resets module)
EOS
newvalues(:present, :default, :absent, %r{.+})
end
end
9 changes: 9 additions & 0 deletions spec/acceptance/post_transaction_actions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@

describe 'yum::post_transaction_action define' do
context 'simple parameters' do
let(:pre_condition) do
"
package{ 'vim-enhanced-absent':
name => 'vim-enhanced',
ensure => 'absent',
}
"
end

# Using puppet_apply as a helper
it 'must work idempotently with no errors' do
pp = <<-EOS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'the dnf_module_stream provider' do
it 'loads' do
expect(Puppet::Type.type(:dnf_module_stream).provide(:dnf_module_stream)).not_to be_nil
end
end
18 changes: 18 additions & 0 deletions spec/unit/puppet/type/dnf_module_stream_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'spec_helper'

dnf_module_stream = Puppet::Type.type(:dnf_module_stream)
RSpec.describe 'the dnf_module_stream type' do
it 'loads' do
expect(dnf_module_stream).not_to be_nil
end

it 'has parameter module' do
expect(dnf_module_stream.parameters).to be_include(:module)
end

it 'has property stream' do
expect(dnf_module_stream.properties.map(&:name)).to be_include(:stream)
end
end