Sysadmin Mega Thread

- hosts: localhost
  gather_facts: false
  tasks:

    - set_fact:
        teams: "{{  teams
                    | default({})
                    | combine( { item['teamname']:  { 'members': [] }
                                                    | combine(item) } ) }}"
      vars:
        raw_string: |
          teamname:Cougars
          description:Baseball
          member:John,second
          member:Bill,first

          teamname:Rockies
          description:Baseball
          member:Phil,pitcher
          member:Peter,catcher
          member:Wilbur,centerfield

          teamname:marlins
          description:MinorLeague
      loop: "{{ raw_string
                | regex_replace(':', ': ', multiline=true)
                | regex_replace('^member:', '  -', multiline=true)
                | split('\n\n')
                | map('replace', '  -', 'members:@@@  -', 1)
                | map('regex_replace', '@@@', '\n')
                | map('from_yaml') }}"

    - debug:
        var: teams
ok: [localhost] => {
    "teams": {
        "Cougars": {
            "description": "Baseball",
            "members": [
                "John,second",
                "Bill,first"
            ],
            "teamname": "Cougars"
        },
        "Rockies": {
            "description": "Baseball",
            "members": [
                "Phil,pitcher",
                "Peter,catcher",
                "Wilbur,centerfield"
            ],
            "teamname": "Rockies"
        },
        "marlins": {
            "description": "MinorLeague",
            "members": [],
            "teamname": "marlins"
        }
    }
}

Not the worst thing Iā€™ve ever doneā€¦

If thereā€™s an argument you can pass to regex_replace to limit the number of replacements, that would eliminate the need to resort plain replace which canā€™t handle \n.


Edited to include empty members list and consolidate to one set_fact.

2 Likes

This is interesting! Thank you for sharing it!

1 Like
    - set_fact:
        teams: "{{  teams
                    | default({})
                    | combine( { item['teamname']:  { 'members': [] }
                                                    | combine(item) } ) }}"

So that creates a variable called ā€œteamsā€ it then takes ā€œteamsā€ and must turn it into a dictionary. It then creates an anonymous dictionary with two items: a (list???) "teamname and a dictionary object ā€œmembersā€ which maps to a list and combines them into an dictionary and then combines that dict with ā€œteamsā€

Not sure if thatā€™s right, but that what seems to be going on

It loops over the ā€œraw_stringā€ output and replaces all ā€œ:ā€ with ": ", setting multiline to true
replaces any line that starts with ā€œmember:ā€ with a ā€™ -ā€™, (must be making it a list item)
creates a list and split on the double \n
no idea what

Ahh I see now, youā€™re creating the list by replacing @@@ with a new line. Not sure what the 1 does after that.

1 Like

I can write a breakdown later today. This is exactly the kind of thing Iā€™ve been doing for the past 6 months so Iā€™ve gotten used to reading and writing relatively complex filter sequences.

1 Like

This is something I gotta get proficient at so if you have the time it would be really helpful.

1 Like

Yep, all correct.

That is the messy part. We need to insert members: above the first - replacement we made before the split, otherwise we have an unnamed list floating in the middle of the dictionary:

description: Baseball
  - Phil,pitcher
  - Peter,catcher
  - Wilbur,centerfield
teamname: Rockies

needs to become:

description: Baseball
members:
  - Phil,pitcher
  - Peter,catcher
  - Wilbur,centerfield
teamname: Rockies

Ideally, this would be done with one filter, but afaik, replace_regex has no clean way of limiting the number of replacements. replace does (the 3rd argument for replace is an integer limiting the number of replacements), but in my testing, replace would automatically escape backslashes, so \n became \\n. So as a workaround, I used replace to insert members: once before the first dash and replace_regex to insert the newline via @@@ which is an arbitrary string that should never appear organically in the data.

replace:

description: Baseball
members:@@@  - Phil,pitcher
  - Peter,catcher
  - Wilbur,centerfield
teamname: Rockies

replace_regex:

description: Baseball
members:
  - Phil,pitcher
  - Peter,catcher
  - Wilbur,centerfield
teamname: Rockies

Then of course itā€™s still just a string, so from_yaml actually interprets it into an object and then you have a list of teams, but you want a dictionary of teams with the keys named after each teamname variable. And if a team has no members, the members variable is simply missing. Based on the samples you provided, if no members are present, you want members to be an empty list.

Both of those issues are rectified here:

This takes each team object from the list, assigns it to a key named after the teamname variable and defaults members to an empty list.

Also, @cotton, if youā€™re spending a lot of time parsing similarly structured text in Ansible, you should look into creating a custom parser:

https://docs.ansible.com/ansible/latest/network/user_guide/cli_parsing.html

This was originally part of the network suite and intended to parse output from things like Cisco switches, but recently, I think they realized this stuff is much more generally applicable and have moved it into utils.

What I did above makes sense for a one-off situation but it really scales badly if your actual use case is more complex or is central to multiple roles or playbooks.

This hacker-factor on that is amazing.

Thatā€™s a nice way to handle that problem. I understand what you did and thatā€™s a good solution to a tough problem.

Thank you for sharing that.

cotton

1 Like

What does ā€œaccess reconciliationā€ mean in the context of compliance requirements in a security baseline document? Itā€™s listed under access control.

I feel like sysadmin speak is like pretend english oftentimes - full of keyword corporate speak techy mumbo jumbo.

1 Like

I canā€™t use ansible any longer for configuration management. So, Iā€™m looking at Chef and Puppet.

Can anyone help give me advice on what I should be looking out for with these tools? Iā€™ve used ansible pretty much daily for the past two years. So I have some familiarity with configuration management, but not really sure what to look out for when deciding on these two.

Anyone have advice on things they learned after starting using puppet or chef they wished they knew before they started using it? Or, can you summarize the tools in like a sentence each?

I understand it can be a complex topic so if I can provide specific things for clarification please let me know.

Edit: I see it says that puppet is ā€œmodelā€ driven and chef is ā€œcodeā€ driven. When would model driven be advantagous compared to code driven?
cotton

Chef is Ruby based whereas all the other ones are python based.

Not sure about Puppet never used it.

May I ask why you cannot use ansible?

With ansible there is a one ā†’ many relationship where one system (ansible server) has to intialize connections to many clients. Chef and Puppet are many ā†’ one relationship where many clients initiate a connection to one master

Or you can use Saltstack which allows you to configure masterless or agentless management :upside_down_face:

I need to check that out.

For the environment Iā€™m in: the one ā†’ many push setup for ansible makes me jump through a lot of hoops to get it to work, which I think is way out of the ordinary deployment. Therefore, Iā€™m looking to pivot.

BTW Iā€™m seriously not trying to start a flame war, because this could turn into that. Iā€™m literally just looking for options and what to look out for when deciding this.

1 Like

traditional master->minion setup (run from a master)

sudo salt 'myhost' cmd.run 'echo "Hello, World!"'

masterless minion setup (run from the minion)

sudo salt-call --local cmd.run 'echo "Hello, World!"'

agentless setup (run from a master but minion install not needed only an entry in /etc/salt/roster)

sudo salt-ssh 'myhost' cmd.run 'echo "Hello, World!"'

1 Like

nah its alright fam. I love RedHat but they gotta stop with the walled garden shit with their tools.

1 Like

How do you initiate the salt-call?

For example, letā€™s say I want to make sure minion1 has apache installed. How does that get called?

With ansible itā€™d be like on ansible server run ansible-playbook configure_http -i ā€¦

The problem that I face is action has to be driven by the client. It canā€™t be driven by the masterā€¦

on RedHat family

sudo yum install salt salt-minion

on Debian family

sudo apt install salt-common salt-minion

One does not have the the minion.service running because the salt-call will instantiate a minion process regardless of a traditional or masterless setup. Just that the package exists.

By default there needs to be a /srv/salt for your state files (yaml) and /srv/pillar for pillar data.

If doing a traditional master->minion it would go like this for the cli

sudo salt 'myhost' pkg.install apache

but if we were to use state files

/srv/salt/apache/apache.sls

httpd:
  pkg.installed: []
  service.running:
    - enable: True
    - reload: True
    - watch:
      - file: /etc/httpd/conf.d/*.conf
    - require:
      - pkg: httpd

And then apply the state

sudo salt 'myhost' state.apply apache

If it were were doing local then it would be.

sudo salt-call --local state.apply apache
1 Like