How to write idempotent script

Hello,

I’m discovering RouterOS scripting.
I often get errors because I’m trying to add something that already exists.

For instance, if I run twice a script that includes the following lines, it would fail the second time.
/interface vlan
add interface=bridge1 name=vlan2 vlan-id=2

How can I solve this ?
Is there anything like “delete ifexists” like in SQL scripts ?

Regards

Better to ask this in the scripting section of the forum.

:do {add interface=bridge1 name=vlan2 vlan-id=2} on-error={}

Its better to test to see if vlan is already created, and if not, create it. I was told (by rextended) its better to fix the problem and do not use on-error

{
/interface vlan
:if ([:len [find where interface=bridge1 vlan-id=2]]=0) do={
	add interface=bridge1 name=vlan2 vlan-id=2
}
}

Script tries to find vlan2/bridge1 and if len=0 then it does not exists. Then create the vlan

First { and last } can be removed. Its just for cut and past to terminal to see if it works. In a script its not needed.

As you know I disagree on a lot of things with rextended. The on-error here is very useful and also in general.

Here because it is a typical “user pressed button twice” case. Using then :if when a value needs to be changed, if currently not as expected.

@Jotne: thanks for the citation




/interface vlan
# if exist, delete it
remove [find where interface=bridge1 name=vlan2 vlan-id=2]
# create (again)
add interface=bridge1 name=vlan2 vlan-id=2

If not exist, create it, if exist, delete it before create again, just for fun, is a test:
:global delifexist do={ [:parse (“$1 ; remove [find where $2] ; add $2”)] }

$delifexist “/ip firewall mangle” “action=accept chain=prerouting disabled=yes comment=test”
:global delifexist do={ [:parse (“$1 ; remove [find where $2] ; add $2”)] }

$delifexist “/ip/firewall/mangle” “action=accept chain=prerouting disabled=yes comment=test”
Obviously is only a test for fun, the better way is what Jotne wrote

…because of this.

Now you type like this, but in a while you used my method to download large files in multiple parts without a fuss, huh?
:laughing:

Of course there is a definite difference between “add vlan2 and ignore the error when it already exists” and “first remove vlan2 if it exists and then add it again”.
This has a totally different effect on the resulting router configuration when vlan2 already existed and had further config attached to it (like IP address, interface list membership, etc).

So which solution you choose totally depends on what requirements you have.

the “delifexist” is just for fun, to test and demostrate the power of routeros scripting,
which obviously could have more power if it were better implemented…
like “time” problem, missing basic functions like replace on string, etc.

Well, a definite improvement would be to have an “ignore minor errors” mode in script execution.
The “on-error” clause acts upon individual blocks so it would have to be applied to every line separately (e.g. using a construct as you showed).
However, in daily practice it is often very inconvenient that one cannot just /import a script and have it go until it encounters a really fatal error.

For example, I sometimes try to transfer config from one router to another using a /export file, and the routers are not completely identical.
E.g. one is a CCR and the other is a CHR I keep as a backup in case the CCR hardware fails.
Or one is a RB2011 and the other is a RB4011 I bought as an upgrade.

The /export files contain minor details the target router does not support, e.g. config for LED, LCD, IP cloud, slightly different SFP interface name, etc.
One has to carefully edit out every config item not supported on the target, or else the whole /import fails (worse: it fails halfway and leaves the router in the half-configured state).

It would be so much more convenient when one could use something like “/import filename ignore-errors=yes” and it would just print the error message (including the line that caused it!) and go on with the next line.
That would also have to be the default for “reset configuration and run script”, where it would also put the error log into some file.
Or there could be a new command “/system script ignore-errors=yes log=file” that you could put on top of such a /import file.

You just smashed your own windows…again. It’s so sad.

This can be implemented (just for uncomplete example) using a text editor than support RegEx or the replace of “carriage return/new line”, and replacing

\r\n/

with

} on-error={}\r\n:do {/

on the export file before importing it,
or something like that until mikrotik implement the correct way…

Obviously can be writed a script than import the .rsc but is really complex evaluate each line and find and report the errors founded.

I think that would only work when /export is always used with the “terse” option, which I usually forget to do.

why not?
for sure is more readable the terse version…

all are like after cleaning “#” parts and fixing the end with final “} on-error={}”

:do {/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik} on-error={}
:do {/ip firewall mangle
add action=accept chain=prerouting disabled=yes} on-error={}
:do {/system routerboard settings
set auto-upgrade=yes cpu-frequency=auto} on-error={}
:do {/interface wireless
security-profiles
set [ find default=yes ]
supplicant-identity=
MikroTik} on-error={}
:do {/ip firewall mangle
add action=accept chain=
prerouting disabled=
yes} on-error={}
:do {/system routerboard
settings
set auto-upgrade=yes
cpu-frequency=auto} on-error={}
:do {/interface wireless security-profiles set [ find default=yes ] supplicant-identity=MikroTik} on-error={}
:do {/ip firewall mangle add action=accept chain=prerouting disabled=yes} on-error={}
:do {/system routerboard settings set auto-upgrade=yes cpu-frequency=auto} on-error={}

I started a project on this but RouterOS/Mikrotik support could not provide me with all supported menu entries for that specific router, so that project it is on hold till that is possible.

http://forum.mikrotik.com/t/project-smart-configuration-export-import/151597/4

Any of the above approaches above can work to re-write an exported config, pick you poison. All have pro/cons:

  • the issue with “delete then add” approach is you can lose access the router by accident pretty easily (e.g. the time between the add and remove)
  • wrapping on-error={} around an “add” is simple, but that not going to cause the attribute values to get set again on import if “reseting” attributes was desired
  • the more complex “IF exists, use ‘set’ ELSE use ‘add’” logic is needed for each line want to override attributes too when using import.

The key thing to know is import and export are NOT really symmetrical. export does represent the config, but in variety of ways. Like whether “sensitive” data is included in the export, which dramatically effect “importability”. While import is basically the same as saying “run script from this file”, and it doesn’t care if the file was from an export – so “import” is more like “run script”, than just a “load config”.

At a more basic level, if your trying to save the config to then import on SAME box, you may be better off just using the /system/backup to save all the config+data. If what you want is to clone a config to another box then use /export on one, and /import after a /system/reset-configuration no-default=yes on the new one works.

It’s the “create-if-missing” option that missing in “set” if you ask me. Otherwise, it just a lot of extra code needed to do same thing, as seen here.

Thank you all for your replies and specifically for this last one !

When I export a config, I often wondered and still wonder what is the specific device state the device needs to be in to cleanly apply the exported data.
If I need to reset the device to blindly apply the exported data, may this data should start with a “reset” statement.

Anyway thanks again for your replies !

you can not put “reset” inside (re)config file
must be doed separately, waith the reboot, then apply the (new) config line-by-line on terminal for see if something cause error(s)

Life is hard, anyway :wink:)