how to define groups_secondary in users

19 views
Skip to first unread message

Jerome Jean Verleyen

unread,
Aug 13, 2026, 12:48:28 PM (11 days ago) Aug 13
to help-cfengine
Dear all

I'm trying to define users in a server. As there will be more than one, i want to use de auto-loop capabilities of Cfengine to create the users.
Howerver, i face an issue when i try to create a first user. I could not give the slist of gropus_secondary as an slist, Cfengine declare that it's a scalar.
Here an excerpt of the code

  vars:
    "users[jerome][policy]" string => "present";
      "users[jerome][uid]" string => "1001";
      "users[jerome][group_primary]" string => "uusmb";
      "users[jerome][groups_secondary]" slist => {"academico"};

      "l_users" slist => getindices("users");
  
  users:
    am_policy_hub::
      "$(l_users)"
      policy => "$(users[$(l_users)][policy])",
      uid => "$(users[$(l_users)][uid])",
      group_primary => "$(users[$(l_users)][group_primary])",
groups_secondary => "$(users[$(l_users)][groups_secondary])";

That's get me back this error:

... error: Attempted to give a scalar to a non-scalar type groups_secondary => "$(users[$(l_users)][groups_secondary])",

As $(users[$(l_users)][groups_secondary]) should get me back an slist, i could not understand where is my error.

Hopes someone could explain me.

Have a great day!

Nick Anderson

unread,
Aug 14, 2026, 8:59:06 AM (10 days ago) Aug 14
to jerome....@ibt.unam.mx, help-c...@googlegroups.com

Dear all

I'm trying to define users in a server. As there will be more than one, i want to use de auto-loop capabilities of Cfengine to create the users. Howerver, i face an issue when i try to create a first user. I could not give the slist of gropus_secondary as an slist, Cfengine declare that it's a scalar. Here an excerpt of the code

vars: "users[jerome][policy]" string => "present"; "users[jerome][uid]" string => "1001"; "users[jerome][group_primary]" string => "uusmb"; "users[jerome][groups_secondary]" slist => {"academico"};

"l_users" slist => getindices("users");

users: am_policy_hub:: "\((l_users)" policy => "\)(users[\((l_users)][policy])", uid => "\)(users[\((l_users)][uid])", group_primary => "\)(users[\((l_users)][group_primary])", groups_secondary => "\)(users[$(l_users)][groups_secondary])";

That's get me back this error:

… error: Attempted to give a scalar to a non-scalar type groups_secondary => "\((users[\)(l_users)][groups_secondary])",

As \((users[\)(l_users)][groups_secondary]) should get me back an slist, i could not understand where is my error.

Hopes someone could explain me.

Have a great day!

Hi Jerome,

The groups_secondary attribute expects a list type but it got a string.

When you store an slist and dereference it with $(), CFEngine resolves it as a scalar and iterates over the list elements individually.

Scalar expansion iterates

$(array[key]) always produces a scalar. When the underlying value is an slist, CFEngine iterates — producing one promise evaluation per list element rather than passing the whole list at once.

bundle agent main
{
  vars:
      "users[jerome][groups_secondary]"  slist  => { "adm" };
      "users[alice][groups_secondary]"   slist  => { "adm", "daemon" };
      
"l_users" slist => getindices("users")
;

  reports:
      "SCALAR $(l_users): $(users[$(l_users)][groups_secondary])";
}
R: SCALAR alice: adm
R: SCALAR alice: daemon
R: SCALAR jerome: adm

Note that alice appears twice, once for each element.

If you use that with groups secondary, it will iterate through

Wrapping the scalar in {} makes it syntactically an slist, so CFEngine accepts it. With a single secondary group this appears to work, but with multiple groups, CFEngine iterates and evaluates the promise once per group element. Each iteration calls usermod -G with one group, replacing the previous, so only the last group survives.

bundle agent main
{
  methods:
      "setup"  usebundle => setup_groups;
      "create" usebundle => create_users;
      "verify" usebundle => verify_users;
}
bundle agent setup_groups
{
  commands:
      "/usr/sbin/groupadd -f testuser";
}
bundle agent create_users
{
  vars:
      "users[testuser][policy]"            string => "present";
      "users[testuser][uid]"               string => "1501";
      "users[testuser][group_primary]"     string => "testuser";
      "users[testuser][groups_secondary]"  slist  => { "adm", "daemon" };
      
"l_users" slist => getindices("users");

  users
:
      
"$(l_users)"
        policy           => "$(users[$(l_users)][policy])",
        uid              => "$(users[$(l_users)][uid])",
        group_primary    => "$(users[$(l_users)][group_primary])",
        groups_secondary => { "$(users[$(l_users)][groups_secondary])" };
}
bundle agent verify_users
{
  commands:
      "/bin/grep testuser /etc/group";
}
# cf-agent -b main -K -I -f /tmp/cfengine3-2OdfsN
    info: Using command line specified bundlesequence
    info: Executing 'no timeout' ... '/usr/sbin/groupadd -f testuser'
    info: Completed execution of '/usr/sbin/groupadd -f testuser'
    info: Created user 'testuser'
    info: Modified user 'testuser'
    info: Executing 'no timeout' ... '/bin/grep testuser /etc/group'
  notice: Q: ".../bin/grep testu": daemon:x:1:testuser
Q: ".../bin/grep testu": testuser:x:1000:
    info: Last 2 quoted lines were generated by promiser '/bin/grep testuser /etc/group'
    info: Completed execution of '/bin/grep testuser /etc/group'

Note the Created + Modified — that is two separate calls. The user ends up in only the last group.

Whole lists @(...)

With anything that would return the list you want e.g. getvalues()

getvalues() returns and slist, this shows dynamically pulling a list and still just One Created call (no modification), both groups applied.

bundle agent main
{
  methods:
      "setup"  usebundle => setup_groups;
      "create" usebundle => create_users;
      "verify" usebundle => verify_users;
}
bundle agent setup_groups
{
  commands:
      "/usr/sbin/groupadd -f testuser";
}
bundle agent create_users
{
  vars:
      "users[testuser][policy]"            string => "present";
      "users[testuser][uid]"               string => "1501";
      "users[testuser][group_primary]"     string => "testuser";
      "users[testuser][groups_secondary]"  slist  => { "adm", "daemon" };
      
"l_users" slist => getindices("users");

  users
:
      
"$(l_users)"
        policy           => "$(users[$(l_users)][policy])",
        uid              => "$(users[$(l_users)][uid])",
        group_primary    => "$(users[$(l_users)][group_primary])"
,
        groups_secondary => getvalues("users[$(l_users)][groups_secondary]");
}
bundle agent verify_users
{
  commands:
      "/bin/grep testuser /etc/group";
}
# cf-agent -b main -K -I -f /tmp/cfengine3-UgVVZb
    info: Using command line specified bundlesequence
    info: Executing 'no timeout' ... '/usr/sbin/groupadd -f testuser'
    info: Completed execution of '/usr/sbin/groupadd -f testuser'
    info: Created user 'testuser'
    info: Executing 'no timeout' ... '/bin/grep testuser /etc/group'
  notice: Q: ".../bin/grep testu": daemon:x:1:testuser
Q: ".../bin/grep testu": adm:x:4:testuser
Q: ".../bin/grep testu": testuser:x:1000:
    info: Last 3 quoted lines were generated by promiser '/bin/grep testuser /etc/group'
    info: Completed execution of '/bin/grep testuser /etc/group'

Nick Anderson

unread,
Aug 14, 2026, 9:05:15 AM (10 days ago) Aug 14
to jerome....@ibt.unam.mx, help-c...@googlegroups.com

Hi Jerome,

Sending again …. the whole lists example was missing last time. …

The groups_secondary attribute expects a list type but it got a string.

When you store an slist and dereference it with $(), CFEngine resolves it as a scalar and iterates over the list elements individually.

Scalar expansion iterates

$(array[key]) always produces a scalar. When the underlying value is an slist, CFEngine iterates — producing one promise evaluation per list element rather than passing the whole list at once.

bundle agent main
{
  vars:
      "users[jerome][groups_secondary]"  slist  => { "adm" };
      "users[alice][groups_secondary]"   slist  => { "adm", "daemon" }
;
      
"l_users" slist => getindices("users")
;

  
reports:
      "SCALAR $(l_users): $(users[$(l_users)][groups_secondary])";
}
R: SCALAR alice: adm
R: SCALAR alice: daemon
R: SCALAR jerome: adm

Note that alice appears twice, once for each element.

If you use that with groups secondary, it will iterate through

Wrapping the scalar in {} makes it syntactically an slist, so CFEngine accepts it. With a single secondary group this appears to work, but with multiple groups, CFEngine iterates and evaluates the promise once per group element. Each iteration calls usermod -G with one group, replacing the previous, so only the last group survives.

bundle agent main
{
  methods:
      "setup"  usebundle => setup_groups;
      "create" usebundle => create_users;
      "verify" usebundle => verify_users;
}
bundle agent setup_groups
{
  commands:
      "/usr/sbin/groupadd -f testuser";
}
bundle agent create_users
{
  vars:
      "users[testuser][policy]"            string => "present";
      "users[testuser][uid]"               string => "1501";
      "users[testuser][group_primary]"     string => "testuser";
      "users[testuser][groups_secondary]"  slist  => { "adm", "daemon" }
;
      
"l_users" slist => getindices("users");

  users
:
      
"$(l_users)"
        policy           => "$(users[$(l_users)][policy])",
        uid              => "$(users[$(l_users)][uid])",
        group_primary    => "$(users[$(l_users)][group_primary])",
        groups_secondary => { "$(users[$(l_users)][groups_secondary])" };
}
bundle agent verify_users
{
  commands:
      "/bin/grep testuser /etc/group";
}
# cf-agent -b main -K -I -f /tmp/cfengine3-2OdfsN
    info: Using command line specified bundlesequence
    info: Executing 'no timeout' ... '/usr/sbin/groupadd -f testuser'
    info: Completed execution of '/usr/sbin/groupadd -f testuser'
    info: Created user 'testuser'
    info: Modified user 'testuser'
    info: Executing 'no timeout' ... '/bin/grep testuser /etc/group'
  notice: Q: ".../bin/grep testu": daemon:x:1:testuser
Q: ".../bin/grep testu": testuser:x:1000:
    info: Last 2 quoted lines were generated by promiser '/bin/grep testuser /etc/group'
    info: Completed execution of '/bin/grep testuser /etc/group'

Note the Created + Modified — that is two separate calls. The user ends up in only the last group.

Whole lists @(...)

To pass a whole list use @().

bundle agent main
{
  methods:
      "setup"  usebundle => setup_groups;
      "create" usebundle => create_users;
      "verify" usebundle => verify_users;
}
bundle agent setup_groups
{
  commands:
      "/usr/sbin/groupadd -f testuser";
}
bundle agent create_users
{
  vars:
      "users[testuser][policy]"            string => "present";
      "users[testuser][uid]"               string => "1501";
      "users[testuser][group_primary]"     string => "testuser";
      "users[testuser][groups_secondary]"  slist  => { "adm", "daemon" }
;
      
"l_users" slist => getindices("users");

  users
:
      
"$(l_users)"
        policy           => "$(users[$(l_users)][policy])",
        uid              => "$(users[$(l_users)][uid])",
        group_primary    => "$(users[$(l_users)][group_primary])"
,
        groups_secondary => { "@(users[$(l_users)][groups_secondary])" };
}
bundle agent verify_users
{
  commands:
      "/bin/grep testuser /etc/group";
}
# cf-agent -b main -K -I -f /tmp/cfengine3-PMNpv7
    info: Using command line specified bundlesequence
    info: Executing 'no timeout' ... '/usr/sbin/groupadd -f testuser'
    info: Completed execution of '/usr/sbin/groupadd -f testuser'
    info: Created user 'testuser'
    info: Executing 'no timeout' ... '/bin/grep testuser /etc/group'
  notice: Q: ".../bin/grep testu": daemon:x:1:testuser
Q: ".../bin/grep testu": adm:x:4:testuser
Q: ".../bin/grep testu": testuser:x:1000:
    info: Last 3 quoted lines were generated by promiser '/bin/grep testuser /etc/group'
    info: Completed execution of '/bin/grep testuser /etc/group'

With anything that would return the list you want e.g. getvalues()

getvalues() returns and slist, this shows dynamically pulling a list and still just One Created call (no modification), both groups applied.

bundle agent main
{
  methods:
      "setup"  usebundle => setup_groups;
      "create" usebundle => create_users;
      "verify" usebundle => verify_users;
}
bundle agent setup_groups
{
  commands:
      "/usr/sbin/groupadd -f testuser";
}
bundle agent create_users
{
  vars
:
      
"users[testuser][policy]"            string => "present";
      "users[testuser][uid]"               string => "1501";
      "users[testuser][group_primary]"     string => "testuser";
      "users[testuser][groups_secondary]"  slist  => { "adm", "daemon" }
;
      
"l_users" slist => getindices("users");

  users
:
      
"$(l_users)"
        policy           => "$(users[$(l_users)][policy])",
        uid              => "$(users[$(l_users)][uid])",
        group_primary    => "$(users[$(l_users)][group_primary])"
,
        groups_secondary => getvalues("users[$(l_users)][groups_secondary]");
}
bundle agent verify_users
{
  commands:
      "/bin/grep testuser /etc/group";
}

Jerome Verleyen

unread,
Aug 14, 2026, 12:39:55 PM (10 days ago) Aug 14
to Nick Anderson, help-c...@googlegroups.com
Le 14/08/2026 à 07:04, Nick Anderson a écrit :

Hi Jerome,

Sending again …. the whole lists example was missing last time. …

The groups_secondary attribute expects a list type but it got a string.

When you store an slist and dereference it with $(), CFEngine resolves it as a scalar and iterates over the list elements individually.


Dear Nick

First of all thank's a lot for your didactic answer. I'm a bit new in this stuff. But i could not find how to get back a slist to the secondary_groups value.

I learn a lot with this code !

Regards!

-- 
-- Jérôme 
La musique, c'est du bruit qui pense.
	(Victor Hugo)

Nick Anderson

unread,
Aug 14, 2026, 12:42:40 PM (10 days ago) Aug 14
to Jerome Verleyen, help-c...@googlegroups.com
Excellent! Glad you found it helpful.
Reply all
Reply to author
Forward
0 new messages