Ansible shell idempotent notification management
Idempotency means a playbook can run repeatedly and only report a change when it actually makes one, but the Ansible shell module assumes every run changes something. Prompted by a question online, Greg Sowell builds a small playbook in this demo that gives shell commands the same honest reporting as native modules, a problem he says also comes up constantly in network automation.
He starts by creating a file idempotently with ansible.builtin.copy using content and dest, then deletes it with ansible.builtin.shell running rm -fv. Removing a file that does not exist still shows changed, so he registers the task output and adds a changed_when condition that looks for the word removed in stdout, which makes the task report ok whenever rm printed nothing. Along the way he explains gather_facts: false, the fields a registered variable carries such as changed, stdout and stdout_lines, and why he enables update revision on launch on the Ascender project while developing.
The last section extends the idea to failure handling. A failed_when condition marks the task failed when stdout is empty, which halts the play, and ignore_errors: true lets the run log the failure and continue to the recap. Anyone writing shell-based tasks in Ansible or Ascender walks away with a pattern for compliance-friendly change reporting.
Key takeaways
- The shell module reports changed on every run, even for a listing or clock command that cannot alter the system.
- ansible.builtin.copy with content and dest creates a file idempotently, reporting ok on later runs when the file already matches.
- Register the shell output and use changed_when to check stdout, for example whether rm -fv actually printed the word removed.
- A registered variable carries changed, stdout and stdout_lines, and stdout_lines is convenient for viewing or iterating over output.
- failed_when lets you define failure from output, and ignore_errors: true logs the failure while letting the play continue.
- Ascender stores job output, including per-task JSON details, for 90 days by default, and the retention can be adjusted.
Questions this video answers
How do I make an Ansible shell command idempotent?
Register the task output and add a changed_when condition that inspects it. In the demo, rm -fv prints removed only when a file is deleted, so checking for that word in stdout reports ok when nothing happened. Prefer a native module such as copy when one exists, since it is idempotent by design.
What do failed_when and ignore_errors do in an Ansible task?
failed_when replaces the default failure detection with your own condition, useful when a binary reports errors that are really false positives, or when empty output should count as failure. ignore_errors: true records the failure but lets the playbook keep running, so the recap shows the task while the rest of the play completes.
About this video
Some Ansible modules will report changes every time no matter what...say for example the shell module. This demo shows how you can modify this behavior by specifying what actually constitutes a change for a specific task.
This video is part of the Ascender Pro playlist. Browse every CIQ video by product and topic.
Transcript
hey everybody I'm Greg Sowell and welcome to another ciq demo so today I'm going to be building a Playbook sorry I'm laughing because I got somebody over here laughing at me um I'm going to be uh Building A playbook that's going to demonstrate item potency so in a nutshell item potency means I can rerun a Playbook multiple times if I need to make a change I will uh if it doesn't need to make a change it won't right and really anable in Ascender whenever it uh doesn't need to make a change it'll just say okay when it doesn't need to make a change
it'll say change right so it actually will indicate to you all that stuff and why this is important is somebody was asking on the internet well it's like well how do I achieve that with shell commands and so I'm going to show you kind of a way to to sort of fake that and there are other command it's not just in like uh system operations like with Linux or Windows but I run into it a lot in networking as well well right there are ways you can throw commands at something and it assumes it's always making a change and that's definitely the way it works
in uh systems with shell commands so let me pull up my uh VSS code Studio so I'm going to start out writing a Playbook really quick as you all remember you start with three dashes so I did a Playbook the other day kind of a challenge on and this one is item potent shell scripts is what we'll call it I did one on uh like working through errors and problems you're going to run into so I challenge you guys to go and attempt that one first and then watch the video and see how it all plays out but in there I'll cover a lot of
these common tips and tricks so hosts I'm going to be going against Greg Rocky n this is just one of my test hosts in the environment I'm going to do gather facts false and this turns off the uh fact collection portion right so Gathering facts means I will connect to a host and I'll pull a whole bunch of really cool variabl IED information that I can use for reporting or in my automations to do conditionals and stuff like that but honestly if I'm not going to use it I'm going to turn it off and by default I always have it off at the top of
View full transcriptHide full transcript
all my stuff because it saves time right it saves processing time I don't have to wait for that task to connect to these things and pull out that back so there it is I'm going to create a VAR section at the top this is where I put variables that are going to apply to everything in here I created even if I don't use it it's kind of like my um documentation section tasks there we are had my hand on the wrong spot on the keyboard so in here we're going to start one and we're going to name it um uh create a dummy file because
I want to just create a file uh in there and then I think what I'll do is I'll delete it so the create file it's not necessarily going to be the item potent part you know what let's go ahead and uh do it item potently so uh in a shell command let me see what would I do in a shell command let me pull this down right here if I was going to do it in a shell I would do something like um uh I would do an echo um I'm always hungry and if you know me that is absolutely the truth I would pipe
that to test1.txt all right now it's created it and I can cat that file and you can see I'm all was hungry it's in there right that's kind of a a quick and dirty way of creating a file so I could do that on the shell for anable but it's always going to register a change right so even if that file exists in the state that it should it's going to it's going to recreate that file right over and over and over and I don't necessarily want it to do that right like kind of that's the item potent nature of shell stuff it assumes every
time you run a shell command that it's making a change because it technically could be even if I'm just displaying information so um you don't always want it to show that so instead of doing that instead of just issuing because let me show you exactly how I would do it in here I would just copy that I would in here I would do anible do builtin Dosh and then I would throw that command in there and guess what it would fire that off and every single time it would create that file only I don't want it to do that I want to do this in
more of an item potent fashion so I'm going to use anible do builtin do copy I think it's copy that has the is it the content parameter I'm sitting here guessing how about I just look it up really fast uh anible right here anable copy module pop into here really quick go to my example section content yeah there it is right there yeah so it's content and then destination so content is just what's going to be inside the file destination where am I sticking it all right so it should be content and I will say I'm always hungry and then it was dest for Destination and we'll put it in the temp directory Temp and we'll call it test one.
txt so that's the file that it's going to create on my remote host and the contents I've said file will be I'm always hungry now the very first time it's going to create it if I rerun that file already exists in this current state guess what it's not going to do anything it's just going to say Okay so we've got that that's a decent example of item potency now what I'm going to do is I'm going to delete that file delete the dummy file let me capitalize that D otherwise it's going to drive me absolutely crazy and we're doing the shell module so that's anable
do builtin do shell and then I'm going to do uh RM sltm SL test1.txt we'll finish that out and I'll do a dash force and V for verose because I think if I uh if I just do RM test1.txt dforce right see it doesn't actually give me any output it's not actually saying hey anything happened so let me do an LS see it actually did delete it but it didn't tell me anything about it so let me recreate the file and I think if i do- v ah see it actually gives me some output so what I'm going to do is I'm going to collect
that output I'm going to use that to determine whether it um uh is changed or not I'm going to use that for my conditional so I'm going to do- f for force and V for verose so now I'm going to do is you know what I'll just save this one and I'll fire it off and then we'll add some extra stuff to it so I'm going to go ahead and save add a comment and I will synchronize it don't forget to sync your changes I do that every single time then I can't figure out why things don't change so I'm going to use an existing
inventory existing inventory item I have inventories again are just big list of all the stuff I could potentially operate against I'm using uh a generic G repository my miscellaneous one and so in the project section that's where I actually tie these te these things together so uh I will hit the edit button on here so you can see it's just my uh miscellaneous uh my miscellaneous repository with just a bunch of weird random stuff in there so I've really got all the pieces I need so I'm going to come in here to job templates I'm going to click add add job template and I'll say
item potent which is such a weird word to spell uh shell demo click on inventory and I'll my generic inventory yep that one and then project it's going to be my miscellaneous project right there I will select it I'm laughing because my cat's beating up the blinds over here uh Playbook so once I choose my project it actually shows me all of them and I've got my item potent Dash shell in there credentials I'll use just my lab credentials right there so I should be done I can save and let me launch it and let's see what I messed up cuz I always screw out
and through the magic of editing you don't have to wait for any of this we'll just come back when it's done all right well it looks like I had my syntax correct on a lot of this stuff so created the dummy file it says changed it's obviously going to say changed because nothing was there if I actually want more detailed info I can just kind of click on it go to the Json section it'll show me detailed information on all the things that it did which is really cool right so I can go back after the fact by default it's going to store all this stuff in Ascender right like in this interface
it's going to store all of it for 90 days I can adjust that to keep longer shorter can also log all this stuff off but right here we see change right we deleted the file um but honestly even if we didn't delete the file it's still going to show change let me show you a demonstration of that so I will right here I will copy that we'll paste and so I'll say delete a second dummy file only we're going to name it test two right a file that doesn't actually exist save that DD commit sync those changes I'm going to come back over here and
I'm going to relaunch the job so on this project which ultimately ties in my get repository I have an option in there called uh update on launch right so if I go into my projects here I can look for my miscellaneous one I'll edit it really quick and I'll scroll down you say update revision on launch so what that essentially does is whenever I launch a job it will first check um to see if it has the newest version so right so it'll connect to my get repository and'll refresh all those files now it does add a little bit of processing time because it has
to sync that project again um but when I'm developing it's so convenient to just come in here and launch and launch and launch otherwise if I update the Playbook and my get repository I have to come in here and have to manually synchronize it so instead of doing that while developing I keep update revision on launch in there so let's pop back over it has successfully completed I'm going to to output and you can see delete a secondary dummy file there was no file right there's absolutely nothing this I could have just done some kind of informational command I could have like shown the clock
or I could have uh listed out the contents of a directory and it's going to show changed every time even though the command has zero opportunity to actually make a change it's going to show changed right so I don't like that behavior necessarily so I could for operational purposes for whatever reason I could potentially need to only see change when a change happens maybe it's a compliance thing right don't show change unless change actually happen so let's update our Playbook a little bit to make that happen so what I'm going to do is I'm going to save this output I'm going to say register and
registering a variable basically means take the content of everything that happens in this task save it into a variable name and I will put it as uh remove check and then I will add a conditional in here on change so I can tell it hey only register a change when it meets this criteria so I can say changed when and let me set it up here it's a little bit weird if I just want to look in the contents I'm going to say if it says removed right because if I look at my CLI command it says removed and the file name so if it
just has removed in there I know it actually did make a change otherwise if it's just completely null um I think it just shows up as completely null right like if I remove the file again yeah so if I remove a file that doesn't actually exist it just shows nothing right so if it shows up as absolutely nothing then uh we know nothing actually happens so if it has removed in there removed in remove uncore check. stdout so by default whenever you're registering variables it actually collects a whole bunch of things uh one it'll be like remove uncore check.
change it'll tell me whether it thought there was a change or not but then there's also STD out which just takes everything that got spit out and uh it puts it in that variable and there's also STD outdore lines so it'll actually kind of put line breaks in there which is convenient for like viewing some information or or maybe uh iterating over it as as a list uh but here I just want to check hey the remove check STD out like just what came back from it does it have the word removed in there so if it does that means it actually changed uh and
then that should be it so it should be good there let me save that commit let me sync it and then we'll launch it again all right so it actually met the criteria for change right it always will so the very first thing we're doing is Crea that dumy file and it's always going to remove it so let's add that to the second one so we should theoretically see the second one show up as just okay so if I come in here I'm going to change or rather I'm going to copy this actually and copy both of these lines go to the end paste these
in and I'll say remove uh check two is what we make it removed and remove check two save it commit sync those changes let's launch it again all right here we are so now we did the conditional check for the second one there is no file which means it's going to show up as empty so our changed uh condition wasn't met so it actually doesn't say it changed right so this one it definitely did remove it showed removed so it says change this one there was nothing for it to remove so it just says okay totally awesome right so it's complete now you could and
sometimes this is required um you could need to adjust when something error is out so I've had some shell commands when you throw a command at edit whatever like the binary is you're like operating on for whatever reason it wants to register back to the Shell that there was actually an error that it failed for some reason even though it didn't right it's kind of like a false positive so you can adjust that behavior as well so you can say failed win and then set a conditional in here so I'm going to say if um remove check two uh we'll say remove check two STD
out is equal to uh null right like so if uh it is blank then that's a an error right like this condition I you know this isn't maybe necessarily valid or whatever but uh in some binaries before you want to set an arbitrary piece of information in there and this is a quick and simple way of doing it so this instead of just saying okay you know like it it completed all right since it's meeting our failed when condition of oh no the return output was empty it should actually show failed on this job there we have it so it's actually showing failed now this
can halt the operation right if I actually want it to just Reg register that it failed and I want it to actually continue on there's another option I can add in here so I can say ignore errors and I can set that to true and so again this is another task level parameter that says hey if you see errors in here go ahead and continue processing right so it's going to log that it's going to see that it failed but it'll continue processing all the way through so in here I'm going to go ahead and relaunch all right so you can see here that everything
completed successfully it show showed the dummy file created as change the uh deleted dummy file uh actually showed change right because it actually did meet our change criteria it showed remove in there the other task it hit the failed condition because it had you know the the empty STD out but we had ignore errors so it went ahead and completed all the way through and you can look down at the play recap it shows failed zero right so it actually allowed it to to completely move all the way through now we could adjust this where uh we add some asserts that's another in way of
looking for um failures or conditional stuff to kind of uh break your playbook to like make it actually stop in there so there's other modifications you can do I'm really curious if you would tweak or tune this did this give you some additional Insight on how you can maybe potentially bake some item potency into your playbooks using commands that have unexpected results in there um if you have any questions comments again throw them our way we'd love to help folks I would love to help engage if you have any need for automation would love to come in help you build your uh automation practice help
build playbooks help build out your Ascender infrastructure let us know how we can help so if nothing else happy automating happy uh item poten thing and we'll see you next time bye
Built for scale. Chosen by the world’s best.
2.75M+
Rocky Linux instances
Being used world wide
90%
Of fortune 100 companies
Use CIQ supported technologies
250k
Avg. monthly downloads
Rocky Linux
9
Enterprise products
Spanning the kernel to the orchestrator
Have questions about your infrastructure?
Talk to a CIQ engineer about Rocky Linux, HPC, and AI infrastructure.
