Create cloud-init virtual machine configuration files in F#.
FsCloudInit includes configuration builders for many common tasks to simplify building the cloud-config file while also providing type checking around configuration code.
#r "nuget: FsCloudInit"
open FsCloudInit
open FsCloudInit.Builders
cloudConfig {
package_upgrade true
add_packages [
"curl"
"httpd"
]
}
|> Writer.write
|> System.Console.WriteLine
This code outputs a simple cloud-config file.
#cloud-config
package_upgrade: true
packages:
- curl
- httpd
Builders can reduce some of the complexity by handling things like type casting and using sane defaults, but it's also possible without the builder.
#r "nuget: FsCloudInit"
open FsCloudInit
{
CloudConfig.Default with
Packages = [ Package "httpd" ]
PackageUpgrade = Some true
}
|> Writer.write
|> System.Console.WriteLine
Often the configuration is more complex or even has external dependencies, and we get the benefits of a full language and framework.
This configuration will install the dotnet 6.0 SDK on a new VM. This pulls the Microsoft package source and signing key when building the cloud-init configuration.
async {
use http = System.Net.Http.HttpClient ()
let! aptSourceRes = http.GetAsync "https://packages.microsoft.com/config/ubuntu/20.04/prod.list" |> Async.AwaitTask
let! aptSourceVal = aptSourceRes.Content.ReadAsStringAsync () |> Async.AwaitTask
let! gpgKeyRes = http.GetAsync "https://packages.microsoft.com/keys/microsoft.asc" |> Async.AwaitTask
let! gpgKey = gpgKeyRes.Content.ReadAsStringAsync () |> Async.AwaitTask
cloudConfig {
add_apt_sources [
aptSource {
name "microsoft-prod"
key gpgKey
source aptSourceVal
}
]
package_update true
add_packages [
Package "apt-transport-https"
PackageVersion (PackageName="dotnet-sdk-6.0", PackageVersion="6.0.100-1")
]
}
|> Writer.write
|> Console.WriteLine
}
The above snippet writes a cloud-init configuration file to the console, resulting in the following configuration file that can be used to install the SDK on a server:
#cloud-config
apt:
sources:
microsoft-prod:
key: >
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v1.4.7 (GNU/Linux)
mQENBFYxWIwBCADAKoZhZlJxGNGWzqV+1OG1xiQeoowKhssGAKvd+buXCGISZJwT
LXZqIcIiLP7pqdcZWtE9bSc7yBY2MalDp9Liu0KekywQ6VVX1T72NPf5Ev6x6DLV
7aVWsCzUAF+eb7DC9fPuFLEdxmOEYoPjzrQ7cCnSV4JQxAqhU4T6OjbvRazGl3ag
OeizPXmRljMtUUttHQZnRhtlzkmwIrUivbfFPD+fEoHJ1+uIdfOzZX8/oKHKLe2j
H632kvsNzJFlROVvGLYAk2WRcLu+RjjggixhwiB+Mu/A8Tf4V6b+YppS44q8EvVr
M+QvY7LNSOffSO6Slsy9oisGTdfE39nC7pVRABEBAAG0N01pY3Jvc29mdCAoUmVs
ZWFzZSBzaWduaW5nKSA8Z3Bnc2VjdXJpdHlAbWljcm9zb2Z0LmNvbT6JATUEEwEC
AB8FAlYxWIwCGwMGCwkIBwMCBBUCCAMDFgIBAh4BAheAAAoJEOs+lK2+EinPGpsH
/32vKy29Hg51H9dfFJMx0/a/F+5vKeCeVqimvyTM04C+XENNuSbYZ3eRPHGHFLqe
MNGxsfb7C7ZxEeW7J/vSzRgHxm7ZvESisUYRFq2sgkJ+HFERNrqfci45bdhmrUsy
7SWw9ybxdFOkuQoyKD3tBmiGfONQMlBaOMWdAsic965rvJsd5zYaZZFI1UwTkFXV
KJt3bp3Ngn1vEYXwijGTa+FXz6GLHueJwF0I7ug34DgUkAFvAs8Hacr2DRYxL5RJ
XdNgj4Jd2/g6T9InmWT0hASljur+dJnzNiNCkbn9KbX7J/qK1IbR8y560yRmFsU+
NdCFTW7wY0Fb1fWJ+/KTsC4=
=J6gs
-----END PGP PUBLIC KEY BLOCK-----
source: >
deb [arch=amd64,armhf,arm64] https://packages.microsoft.com/ubuntu/20.04/prod focal main
package_update: true
packages:
- apt-transport-https
- - dotnet-sdk-6.0
- 6.0.100-1
Write some arbitrary data to a file. It will be base64 encoded automatically so there won't be any character escaping issues.
cloudConfig {
write_files [
writeFile {
path "/var/lib/data/hello"
content "hello world"
owner "root:root"
permissions "400"
}
]
}
|> Writer.write
If you are writing files that should be owned by users other than root
, you often need to use defer
so they are not written until after that user exists. When not deferred, depending on the version of cloud-init, the file may not be written or it may end up owned by root
.
cloudConfig {
write_files [
writeFile {
path "/var/lib/data/hello"
content "hello world"
owner "myuser:myuser"
defer true
}
]
}
|> Writer.write
cloudConfig {
package_upgrade
add_packages [
"curl"
"screen"
"httpd"
]
}
|> Writer.write
cloudConfig {
run_commands [
[ "ls"; "-l"; "/" ]
[ "sh"; "-c"; "date >> whatsthetime.txt && cat whatsthetime.txt" ]
"apt update".Split null
]
}
|> Writer.write
cloudConfig {
final_message "#### Cloud-init is done! ####"
}
|> Writer.write
cloudConfig {
power_state (
powerState {
mode PowerState.Mode.Reboot
message "Done with installation. Rebooting now."
}
)
}
cloudConfig {
attach_ubuntu_pro (
ubuntuPro {
token "d6cec6a05314b7c63f251e2c0e238830"
}
)
}
cloudConfig {
attach_ubuntu_pro (
ubuntuPro {
token "d6cec6a05314b7c63f251e2c0e238830"
enable [
UbuntuPro.Services.FipsPreview
UbuntuPro.Services.EsmApps
UbuntuPro.Services.EsmInfra
]
}
)
power_state (
powerState {
mode PowerState.Mode.Reboot
message "Rebooting to enable FIPS kernel."
}
)
}
cloudConfig {
users [
user {
name "itme"
gecos "My Account"
ssh_import_github_id "mygithubusername"
groups [ "sudo" ]
sudo Sudo.AllPermsNoPasswd
}
]
}