I did this after seeing this before on HN. There were a few processes that were manual that benefitted from the technique in the article.
Learn from my folly: I even called them “do nothing scripts,” referencing this article. However; I was judged by peers for not writing the full automation versions as they didn’t appreciate the idea of gradual automation (programmer hubris?). Saying “do nothing scripts” in meetings did catch the awkward attention of leadership.
As a description, “do nothing” communicates a lot. As a brand, “do nothing” can use some improvements.
My short prescription of turning “do nothings” into “do some things” into “do all the things” didn’t help. We had some new people join the team and they had fun turning the do nothing scripts into a document. * Sigh *
I still build these type of process description scripts still. I usually don’t advertise them to peers until they do some of the things nowadays.
I like the term "scaffold". It intuitively conveys that this is something quickly and easily started and which can stand on its own, but at the same time is fundamentally incomplete and a stepping stone towards something more permanent of greater value.
"Process Automation" would be a good, encompassing term I think.
Even if there's no code being run, having all the steps down and easy to follow is a way to make the process "automatic". The term is extendible to completely automated computer code, while including non-computer-code (human) automation.
>My short prescription of turning “do nothings” into “do some things” into “do all the things” didn’t help. We had some new people join the team and they had fun turning the do nothing scripts into a document. * Sigh *
You know, it might be useful to do a little bit of extra work and turn it into its own exportable documentation so that people can't do that and have to rely on the script to get the documentation for it.
I think they should be called Runbook Scripts. They basically walk you through a procedure with a text interface. I bet they would work even better as a slide deck.
"Immediate" is meant to accentuate that they can be deployed and start delivering value right now, something that (usually) can't be said for the implementation of actual automation.
And "placeholder" is meant to convey that you don't intend to stop there.
What do nothing scripts automate is on the human side, particularly the shift of focus. It's perhaps correct to call them along the lines of "thought automation" or "decision automation" scripts.
Pet peeve: historical reasons aside, "inductive programming" (as in mathematical induction) is a more accurate/descriptive name than "dynamic programming".
You could call it an "E-I Script" for Efficiency Interest Script. Over time your costs are gradually lowered as each step is automated - like accruing interest in a savings account.
My experience in corporate IT convinced me that even if an entire release could be automated down to a single press of a giant red button (and even if undoing the release could be another, single button), then culturally our organization would still figure out a way to turn the pressing of that button into a six hour ordeal requiring the participation of twelve people.
My empirical evidence backs this up, I had deploying new releases to all servers completely automated down to just creating a tag in GitLab and every time the decision of actually running it turned into a series of emails and meetings throughout the week.
The idea here can be generalized to a primitive programming construct: The magic function "wish", which can do anything you want, just give it a string description.
For example:
wish('copy these files to this host', files, host)
You can augment this further by allowing the program to specify a return type from the wish, and magically the wish will produce the type you want. So for example:
data = wish(Path, 'the path of the data files I need')
Of course, the wish is actually implemented by sending a request to some human user. In my "wish" library for Python http://rsyscall.org/wish/ you can have a stack of handlers for wishes, just like exception handlers. If later you want to automate some wish, you can specify a handler which intercepts certain wishes and forwards the other ones on (by wishing again, just like an exception handler can re-raise).
No offense, but this seems like unnecessary indirection harmful to script legibility, e.g. it would help me as a reader to see, instead, `scp.copy(files, host)`, instead of having to browse through a list of wish handlers to see which one implements file copy.
Yeah I agree, you'd only want to automate it that way if you couldn't modify the original script, which is kind of a niche use case... anyway I just think it's neat
Similar to behavior driven testing. For example behave framework.
It works exactly like wish does above but creates a whole language out of it.
For a human it reads very natural but understanding which combination of human words that map to underlying handlers is a hopeless and unnecessary level of indirection, and is why anyone shouldn’t touch these tools with a ten foot pole.
I like your idea. Im gonna try to use your project soon, but due to my companies policies ill probably have to slowly implement your ideas. If it comes to that I might rename your "wish"es to "requirement"s as that would fit with my company's culture a bit more.
Side note: - I don't understand why classes are needed here.
instead of
procedure = [
CreateSSHKeypairStep(),
GitCommitStep(),
WaitForBuildStep(),
RetrieveUserEmailStep(),
SendPrivateKeyStep(),
]
for step in procedure:
step.run(context)
Why not make them functions instead of classes. The URLs can be locals instead of class variables and the context argument becomes an argument to the function rather than a run method.
procedure = [
createSSHKeypairStep,
gitCommitStep,
waitForBuildStep,
retrieveUserEmailStep,
sendPrivateKeyStep,
]
for step in procedure:
step(context)
The OO is just gratuitous. Personally I probably wouldn't even bother with the for loop/context and would just pass the data that's actually needed by each function into it manually.
I get that it feels more modular - "oh all I need to do to add a step is add it to the list!" But like, all you need to do to add a step is add a line of code. There's no difference.
Much easier to see how the data is actually flowing that way. I found the fact that the context was mutated in a function it was passed into kind of gross. It's just globals with extra steps. Besides, this is less LOC anyway.
I agree with you. I currently have a similar process where I jot down the steps in README.md or INSTRUCTIONS.md files. The script is a bit better in that it forces you to do the steps in order and you're less likely to skip a step by accident.
However if I'm going to start creating scripts like this then I think all the OO/Class boilerplate overhead would make me reluctant to do it. For me using functions with docstrings keeps more of the simplicity of the markdown approach while still allowing for gradual automation.
I assume the point is to let any one of the steps turn into something more complex and automated, as they say, without affecting the rest of the script. If each step is its own class, that gives you a place to go in and expand any one of them without worrying about conflicting with unrelated changes.
If the idea were to leave the script as it is, then of course your way is simpler.
Seems like premature abstraction to me. It's not difficult to make that change if you need it. In any case, I'm really struggling to envisage a scenario where you would need them to be classes - why couldn't a more automatic version still be a function?
In my opinion, in order to justify instantiating an object, it should actually contain data, and that data should actually be accessed by code other than methods internal to that class. This rules out using an object to represent a procedure - regular functions subsume that functionality.
Before first class functions became commonplace, it did make sense to use objects as procedures in some OO languages, to allow you to pass them around as values. Since most languages these days do have first class functions, this has become less necessary.
If you want a class in order to to split the procedure into multiple methods with access to shared mutable state, that's totally fine - you can just use nested functions to do the same thing.
I see what you mean. But in Python a function can be turned into a full fledged callable class at a later point without any change to the rest of the script.
Devil's advocate; each class in the array has a consistent interface, making it easier to do common things every step like measurements and logging during iteration etc.
But I agree with you, it's simpler to just call the functions only giving them what they need. This prevents them from implicitly depending on context and makes reasoning about the code much easier....
Why are you generating *private keys* for users, then sharing them? Not that this impacts the automation bits but IMHO users should known how to generate and maintain a key pair, and send you the public key.
Did it all the time. Users weren’t sophisticated. They want a tool they can put a user/pass into and then upload and download. They have maybe some conditions from IT (SFTP okay, S3 okay, encrypted at rest, whatever).
We did this trivially with S3, our implementation guy gives them an access key ID and secret access key, tells them to install Cyberduck, and gives them a URL to paste. We’re off to the races.
Having the user generate the thing will turn going live from hours to days.
I’ve also done this analogously with SFTP. You keep the creds so you can help them because they’ll type it in wrong, their software will fuck it up, whatever.
Agree, it's painful, and it really takes me out of the larger point they are trying to make
A workflow should be be
User creates an SSH key pair, the private key never leaving the computer
User sends public key to authoriser
Authoriser pushes public key to Git (presumably an email + key touple?)
Wait for the build job to finish (not sure what this does)
Build process sends the email saying "You can now use your key"
Same with say a wireguard key. Or an SSL certificate.
I think the larger point is that you just have step by step instructions, and thus dont need to catch edge cases, but it also makes it harder to avoid skipping a step, which seems reasonable (I do this myself in some areas)
Not to weigh in on this particular issue, but I think your objection here is another piece of evidence weighing in the favor of this overall approach. It's perfect to have this debate immediately and so early in the process, before any time is spent in detailed implementation.
It's not a "private" key if its shared. Only the end user needs to know the private key details.
Consider the usecase of generating a user their initial password for a service, this almost always results in the user needing to immediately reset their password after initial login, this doesn't happen if someone is generating both parts of the key pair for you.
My sysadmin doesn't need to know my password, and doesn't need to know my private key or passphrase, that would allow them to impersonate me.
But I wonder if it wouldn't be better if it were something like a ncurses list with checkboxable list items which one would progress trough.
Take a checklist for a pilot for example. Wouldn't it be not as good if the next item on the list only appeared after an item has been checked? It could be beneficial if you could peek a couple of items down the line in order to group your actions or get a better overview of the overall task.
"Do I put the water for the coffee in the microwave now or do I first finish this quick item and let the next long-running item start before I go and make my coffee?"
You lost me at ncurses. IMO this idea works because the task is so simple no one would bat an eye at the thought of doing it. All it takes is one person to question why we're not using <technology> instead, or why we're not spending this ncurses amount of effort towards something else instead, to jeopardize the effort.
I think this would be a great idea if the intent was a checklist that would never be automated. If the idea is to create a scaffold for future incremental automation where tasks start falling off the checklist (until ideally none are left), then I'd rather start with that framework instead.
This approach doesn't work well in the long term. I've worked as a successor in one of the author's previous company. All the "do-nothing scripts" diverges from the reality as time goes by. The scripts became a false prophet, not a single source of truth. When I came in, the fully automated parts are doing fairly well. On the other hands, most the one-off scripts doesn't work.
Falsy docs are worse than no documentation. We've tried looking at the scripts, but small divergents in the scripts destroyed our confidence in the scripts. Instead we figure how to do task by looking at the source code and actual deployments.
The lesson we learned from this is we need to treat scripts as a real software instead. We make scripts idempotent and run them periodically to see if anything breaks. For infrastructures we make them immutable and embraced the cattle mindset(vs pet mindset).
> All the "do-nothing scripts" diverges from the reality as time goes by.
Not sure I follow. In my experience, we have _tons_ of process that's written up in random pages in our company wiki. I assure you, nothing diverges as quickly as a set (regrettably) of duplicated wiki pages outlining a process.
If you're making the argument that a fully automated script is superior to a checklist, then ... well, sure. But while you're spending the resources to fully automate one process, duplicated wiki pages are being written for the 100 other checklists (at least in my experience).
The key here is that I fully agree with you on the divergence problem. But if a scripted checklist diverges, people complain and it's trivial to update the script. If you don't have these, then people either add a comment to one of the wiki pages, or a side note, or worse, write up a "better" wiki page with the real actual this-time-for-sure steps.
I believe the key here is to implement a consistent tactical approach to incremental development, capturing divergence along the way. If you just let them lie fallow, then yes, that's bad. But surely a fully-automated script allowed to lie fallow and diverge from reality is no better off.
does this mean change the commands that run to be idempotent? so the script/echo concept stays the same but you make sure the commands it echo's wont break anything if run more then once?
Not the parent, but I'm guessing it's the idea that the script always moves the system to the same steady state. Similar to how Ansible tries to be and how Kubernetes controller loops work.
Learn from my folly: I even called them “do nothing scripts,” referencing this article. However; I was judged by peers for not writing the full automation versions as they didn’t appreciate the idea of gradual automation (programmer hubris?). Saying “do nothing scripts” in meetings did catch the awkward attention of leadership.
As a description, “do nothing” communicates a lot. As a brand, “do nothing” can use some improvements.
My short prescription of turning “do nothings” into “do some things” into “do all the things” didn’t help. We had some new people join the team and they had fun turning the do nothing scripts into a document. * Sigh *
I still build these type of process description scripts still. I usually don’t advertise them to peers until they do some of the things nowadays.
Actually, other applicable terms could be: a) programmed documentation; b) script as single-source of procedural truth
Short for "print commands to run manually" script
Even if there's no code being run, having all the steps down and easy to follow is a way to make the process "automatic". The term is extendible to completely automated computer code, while including non-computer-code (human) automation.
Deleted Comment
You know, it might be useful to do a little bit of extra work and turn it into its own exportable documentation so that people can't do that and have to rely on the script to get the documentation for it.
I’ve added this logic afterwards.
"Immediate" is meant to accentuate that they can be deployed and start delivering value right now, something that (usually) can't be said for the implementation of actual automation.
And "placeholder" is meant to convey that you don't intend to stop there.
Let's see, e i... e i... Oh! There it is!
For example:
You can augment this further by allowing the program to specify a return type from the wish, and magically the wish will produce the type you want. So for example: Of course, the wish is actually implemented by sending a request to some human user. In my "wish" library for Python http://rsyscall.org/wish/ you can have a stack of handlers for wishes, just like exception handlers. If later you want to automate some wish, you can specify a handler which intercepts certain wishes and forwards the other ones on (by wishing again, just like an exception handler can re-raise).It works exactly like wish does above but creates a whole language out of it.
For a human it reads very natural but understanding which combination of human words that map to underlying handlers is a hopeless and unnecessary level of indirection, and is why anyone shouldn’t touch these tools with a ten foot pole.
Much easier to see how the data is actually flowing that way. I found the fact that the context was mutated in a function it was passed into kind of gross. It's just globals with extra steps. Besides, this is less LOC anyway.
* https://pyvideo.org/pycon-us-2012/stop-writing-classes.html
However if I'm going to start creating scripts like this then I think all the OO/Class boilerplate overhead would make me reluctant to do it. For me using functions with docstrings keeps more of the simplicity of the markdown approach while still allowing for gradual automation.
Here's a simple example:
https://gist.github.com/snth/c5c5a1236dd8ddf91973aed77d66cd9...
If the idea were to leave the script as it is, then of course your way is simpler.
In my opinion, in order to justify instantiating an object, it should actually contain data, and that data should actually be accessed by code other than methods internal to that class. This rules out using an object to represent a procedure - regular functions subsume that functionality.
Before first class functions became commonplace, it did make sense to use objects as procedures in some OO languages, to allow you to pass them around as values. Since most languages these days do have first class functions, this has become less necessary.
If you want a class in order to to split the procedure into multiple methods with access to shared mutable state, that's totally fine - you can just use nested functions to do the same thing.
Not that it matters much either way.
But I agree with you, it's simpler to just call the functions only giving them what they need. This prevents them from implicitly depending on context and makes reasoning about the code much easier....
This is a pattern easier to represent/embed within objects. But you might as well use a library of prefixed functions too.
Deleted Comment
Deleted Comment
> Send the user their private key via 1Password.
Why are you generating *private keys* for users, then sharing them? Not that this impacts the automation bits but IMHO users should known how to generate and maintain a key pair, and send you the public key.
We did this trivially with S3, our implementation guy gives them an access key ID and secret access key, tells them to install Cyberduck, and gives them a URL to paste. We’re off to the races.
Having the user generate the thing will turn going live from hours to days.
I’ve also done this analogously with SFTP. You keep the creds so you can help them because they’ll type it in wrong, their software will fuck it up, whatever.
A workflow should be be
Same with say a wireguard key. Or an SSL certificate.I think the larger point is that you just have step by step instructions, and thus dont need to catch edge cases, but it also makes it harder to avoid skipping a step, which seems reasonable (I do this myself in some areas)
Consider the usecase of generating a user their initial password for a service, this almost always results in the user needing to immediately reset their password after initial login, this doesn't happen if someone is generating both parts of the key pair for you.
My sysadmin doesn't need to know my password, and doesn't need to know my private key or passphrase, that would allow them to impersonate me.
Discussed at the time: https://news.ycombinator.com/item?id=20495739
But I wonder if it wouldn't be better if it were something like a ncurses list with checkboxable list items which one would progress trough.
Take a checklist for a pilot for example. Wouldn't it be not as good if the next item on the list only appeared after an item has been checked? It could be beneficial if you could peek a couple of items down the line in order to group your actions or get a better overview of the overall task.
"Do I put the water for the coffee in the microwave now or do I first finish this quick item and let the next long-running item start before I go and make my coffee?"
Falsy docs are worse than no documentation. We've tried looking at the scripts, but small divergents in the scripts destroyed our confidence in the scripts. Instead we figure how to do task by looking at the source code and actual deployments.
The lesson we learned from this is we need to treat scripts as a real software instead. We make scripts idempotent and run them periodically to see if anything breaks. For infrastructures we make them immutable and embraced the cattle mindset(vs pet mindset).
Not sure I follow. In my experience, we have _tons_ of process that's written up in random pages in our company wiki. I assure you, nothing diverges as quickly as a set (regrettably) of duplicated wiki pages outlining a process.
If you're making the argument that a fully automated script is superior to a checklist, then ... well, sure. But while you're spending the resources to fully automate one process, duplicated wiki pages are being written for the 100 other checklists (at least in my experience).
The key here is that I fully agree with you on the divergence problem. But if a scripted checklist diverges, people complain and it's trivial to update the script. If you don't have these, then people either add a comment to one of the wiki pages, or a side note, or worse, write up a "better" wiki page with the real actual this-time-for-sure steps.
I believe the key here is to implement a consistent tactical approach to incremental development, capturing divergence along the way. If you just let them lie fallow, then yes, that's bad. But surely a fully-automated script allowed to lie fallow and diverge from reality is no better off.