Foreman plugin does not store interfaces changes to database

Hello Foreman Community!

I’ve started a new custom Foreman Plugin for managing CNAMEs to a FQDN: GitHub - XMol/foreman_cnames: Make Foreman maintain a list of CNAME records for an interface. It is based off the Foreman Plugin Template and I stole most of the changes from an old pull request, which was motivated by a feature request to add support of CNAMEs to Foreman UI (#9388).

While I have gathered some experience in Ruby scripting, Rails is completely new to me.

The current HEAD can be installed with successful database migrations and Foreman UI integration. After foreman-maintain service restart, existing CNAMEs are shown in the NIC edit form, where new CNAMEs seemingly can be added, too. However, the problem is…

Problem

New or updated CNAMEs from the NIC edit form are not written to the database.

Foreman and Proxy versions

RedHat Satellite 6.13.7, which includes (among others)…

  • satellite-6.13.7-1.el8sat
  • katello-4.7.0-1.el8sat
  • foreman-3.5.1.24-1.el8sat
  • foreman-proxy-3.5.1-1.el8sat

Distribution and version

My development Foreman instance is running on

  • Red Hat Enterprise Linux release 8.9 “Ootpa” (4.18.0-513.24.1.el8_9.x86_64)

Other relevant data

I can successfully add new CNAMEs to the database through the Rails console.

irb(main):001:0> ForemanCnames::HostAlias.new(name: 'test-alias', domains_id: 2, nics_id: 7958).save
=> true
[root@satellite-test-01 foreman]# sudo -u foreman psql -d foreman -c 'select * from foreman_cnames_host_aliases'
 id |    name    | nics_id | domains_id |         created_at         |         updated_at
----+------------+---------+------------+----------------------------+----------------------------
  1 | test-alias |    7958 |          2 | 2024-06-04 09:20:56.196445 | 2024-06-04 09:20:56.196445
(1 row)

This record does show up in the NIC edit form (although ugly formatted, polish comes later :slight_smile:). But as said before, changes to this or newly added CNAMEs are not written to the database. So my plugin is missing something which I could not figure out.

Running Foreman on debug log level, I see that the Deface overrides are applied, but submits of the NIC edit form are not recorded. I’d be thankful for any comments/pointers towards mistakes and/or omissions in the plugin.

1 Like

Hi again, made one step forward by extending the NIC::Interface parameters (instead of NIC::Base). Now I can see in the log file that a patch was tried and the :host_alias_attributes parameter is not permitted.

logged interface patch
2024-06-11T10:59:11 [I|app|73490761] Started PATCH "/hosts/1937" for 2a00:1398:4:0:65cc:b271:db94:9357 at 2024-06-11 10:59:11 +0200
2024-06-11T10:59:11 [I|app|73490761] Processing by HostsController#update as */*
2024-06-11T10:59:11 [I|app|73490761]   Parameters: {
  ...,
  "host" => {
    "name" => "satellite-test-01",
    "hostgroup_id" => "353",
    "managed" => "true",
    "overwrite" => "false",
    "interfaces_attributes" => {
      "0" => {
        "_destroy" => "0",
        "mac" => "00:50:56:b0:cf:6f",
        "identifier" => "ens192",
        "name" => "satellite-test-01",
        "domain_id" => "1",
        "subnet_id" => "63",
        "subnet6_id" => "105",
        "ip" => "141.52.208.167",
        "ip6" => "2a00:1398:100:2::20",
        "host_aliases_attributes"=> {
          "0" => {
            "name" => "test-alias2",
            "domains_id" => "2",
            "_destroy" => "false",
            "id" => "1"
          }
        },
        "managed" => "1",
        "primary" => "1",
        "provision" => "1",
        "execution" => "1",
        "tag" => "",
        "attached_to" => "",
        "id" => "7958"
      }
    },
    ...
  }
}
2024-06-11T10:59:11 [D|tax|73490761] Current location set to none
2024-06-11T10:59:11 [D|tax|73490761] Current organization set to SCC
2024-06-11T10:59:11 [D|tax|73490761] Current organization set to none
2024-06-11T10:59:11 [D|tax|73490761] Current location set to none
2024-06-11T10:59:11 [D|app|73490761] Unpermitted parameter: :host_aliases_attributes
2024-06-11T10:59:11 [D|app|73490761] Unpermitted parameter: :media_selector
2024-06-11T10:59:11 [W|api|73490761] param host[puppetclass_*] has been deprecated in favor of host[puppet_attributes][puppetclass_*]
2024-06-11T10:59:11 [W|app|73490761] Using TFTP Smart Proxy hostname as the boot server name: satellite-test-01.scc.kit.edu
2024-06-11T10:59:11 [D|tax|73490761] Current organization set to SCC
2024-06-11T10:59:11 [D|tax|73490761] Current location set to CM
2024-06-11T10:59:11 [D|tax|73490761] Current location set to none
2024-06-11T10:59:11 [D|tax|73490761] Current organization set to none
2024-06-11T10:59:12 [I|app|73490761] Redirected to https://satellite-test-01.scc.kit.edu/new/hosts/satellite-test-01.scc.kit.edu
2024-06-11T10:59:12 [D|tax|73490761] Current location set to none
2024-06-11T10:59:12 [D|tax|73490761] Current organization set to SCC
2024-06-11T10:59:12 [I|app|73490761] Completed 302 Found in 1360ms (ActiveRecord: 74.8ms | Allocations: 200316)

Again, I assume the relations between the controllers and models are done correctly, since I can supply :host_aliases_attributes when creating a new Nic::Interface in the Rails console.

irb(main):001:0> Nic::Interface.new(name: 'demo-interface', :host_aliases_attributes => [ { :name => 'demo-alias' } ] ).inspect
=> "#<Nic::Interface id: nil, mac: nil, ip: nil, type: \"Nic::Interface\", name: \"demo-interface\", host_id: nil, subnet_id: nil, domain_id: nil, attrs: {}, created_at: nil, updated_at: nil, provider: nil, username: nil, password: nil, virtual: false, link: true, identifier: nil, tag: \"\", attached_to: \"\", managed: true, mode: \"balance-rr\", attached_devices: \"\", bond_options: \"\", primary: false, provision: false, compute_attributes: {}, execution: false, ip6: nil, subnet6_id: nil>"

irb(main):002:0> Nic::Interface.new(name: 'demo-interface', :host_alias_attributes => [ { :name => 'demo-alias' } ] ).inspect
Traceback (most recent call last):
        3: from lib/tasks/console.rake:5:in 'block in <top (required)>'
        2: from (irb):11
        1: from app/models/concerns/foreman/sti.rb:19:in 'new'
ActiveModel::UnknownAttributeError (unknown attribute 'host_alias_attributes' for Nic::Interface.)

The produced interface doesn’t retain the alias attributes, because they are stored in a different table, as I understand it.

My current theory is, that the model is correct. But the parameters controller is not mixed into the interface’s parameter filter. I expected parameter_filter in engine.rb to do that by some opaque magic, but am evidently mistaken.

The old pull request doesn’t have that issue, because it inserts itself right at the NicBase parameter controller. Since my plugin is namespaced, I can’t do that easily (without resorting to monkey patches).
The How-To-Create-a-Plugin briefly explains how to permit attributes on Foreman models by using parameter_filter. Tracing that in the Foreman sources, I get to Foreman::ParameterFiler#permit, where the comment says, it doesn’t work for nested attributes. My options are to either instantiate a new ParameterFilter or to supply a block to parameter_filter, right?

Using a block with parameter_filter I discover that it is executed through Foreman::ParameterFilter::Context. However, none of the variants I’ve tried so far had any impact, :host_aliases_attributes stayed unpermitted.

With the latest revelations I found Compute resource plugin: Network compute attributes not passed, where @jbm wanted to do something very similar seven years ago. @iNecas explained how the remote execution Foreman plugin (rex) had to be updated regarding the :execution parameter on Nic::Interface. As for @jbm, applying the same solution to my situation made no difference, probably because :host_aliases_attributes isn’t a mere scalar parameter?

# ForemanCnames::Engine
Foreman::Plugin.register :foreman_cnames do
    requires_foreman '>= 3.5.0'

    parameter_filter ::Nic::Interface do |context|
        context.permit(host_aliases_attributes: [])
    end
end

Just to feel some satisfaction, after spending two weeks on this nut without cracking it, I added {:host_aliases_attributes => [host_alias_params_filter]} to the Foreman::Controller::Parameters::NicBase source and found that now updates to interfaces through the GUI work just fine!

Apparently, I was already very close, though I cannot be sure that this is the sole difference that fixed my issue:

# ForemanCnames::Engine
Foreman::Plugin.register :foreman_cnames do
    requires_foreman '>= 3.5.0'

    parameter_filter ::Nic::Interface do |context|
        context.permit :host_aliases_attributes => {}
    end
end

With that, changes in the GUI are propagated to the database!

Next challenge is to find out why these changes are not send to our DNS capsule to add/remove/update the CNAMEs to DNS… If I fail to understand that black magic, I’ll start a new thread.

Just as a new note that might help others googleing for issues with Foreman’s strong parameter filter, this is how you can inspect the current filters for your plugin through the console:

irb(main):001:0> Foreman::Plugin.registered_plugins[:foreman_plugin_name].instance_variable_get('@parameter_filters')