2

I know cloud-init allows to run commands as part of the cloud-init yml

runcmd:
 - [ pwd ]

but I am looking for a way to execute a full shell script.

Some people just pipe the output of a curl into a shell - but that feels like a very wonky approach. Maybe the script could be integrated into the yml via some templating, but that doesn't sound too great either.

Much better would be to ship the script alongside of the yml to the host and refer to it. But I haven't found any documentation on how to do this.

Is this possible?

I am using terraform to setup the server, but I am wondering if this could also done with pure cloud-init.

tcurdt
  • 363
  • 3
  • 9

2 Answers2

1

Try combining it with write_files.

You can write script somewhere (e.g. as /usr/local/bin/myscript.sh) and then do runcmd: /usr/local/bin/myscript.sh

Make sure to set executable permission.

rvs
  • 4,027
  • 1
  • 25
  • 30
  • That's looking promising! But the docs do not clearly specify the lifecycle. Will the `write_files` for sure be run before `runcmd`? – tcurdt Dec 11 '21 at 20:18
  • @tcurdt not sure either. give it a go and see if it works? – rvs Dec 13 '21 at 14:30
  • @tcurdt I would say it depends on what is in /etc/cloud/cloud.cfg From what I've seen, write-files module is happening during init stage while runcmd happens during config stage. – Xerkus Jan 24 '22 at 10:23
1

If you only want to run a script, you can pass the script directly as userdata, instead of using a cloud-config. As long as the script starts with #!, cloud-init will attempt to run it directly, rather than interpreting it as a cloud-config.

If you want to run both cloud-config and a separate script, while the other posted write_files solution will work, you can also pass a mime multi part archive. Cloud-init contains a helper script to make this a little easier. Say you have userdata in a file named my-user-data:

#cloud-config
runcmd:
  - echo 'test-from-cloud-config' > /var/tmp/test_from_cloud_config

And a script you'd like to run callecd test.sh:

#!/bin/bash

echo 'test_from_script' > /var/tmp/test_from_script

You could call:

cloud-init devel make-mime -a test.sh:x-shellscript -a my-user-data:cloud-config > /tmp/userdata

You can take the resulting multi part file and pass it to cloud-init as userdata (e.g. using LXD):

lxc launch ubuntu:focal multi -c user.user-data="$(cat /tmp/userdata)"

You can see that both the cloud-config and the script have run:

root@multi:~# cat /var/tmp/test_from_cloud_config 
test-from-cloud-config
root@multi:~# cat /var/tmp/test_from_script 
test_from_script

See the cloud-init docs for reference.

falcojr
  • 171
  • 2
  • 3
    Since this question is tagged "terraform" I also want to note that [the `cloudinit_config` data source belonging to the `hashicorp/cloudinit` Terraform provider](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/cloudinit_config) is effectively a Terraform-integrated equivalent of `cloud-init devel make-mime`, which can avoid the step of creating a separate temporary file if you happen to be setting things up with Terraform. – Martin Atkins Dec 11 '21 at 01:04
  • Super interesting details. I didn't realize you can also just pass in a script by itself. Good to know the shebang is good enough. – tcurdt Dec 11 '21 at 20:25