# example lxc_host.cfg # cfg data => ' # { # "bridge": "lxcbr0", # "netmask": "255.255.255.0", # "network": "10.0.1.0/24", # "addr": "10.0.1.1", # "dhcp_range": "10.0.1.200,10.0.1.254" # "dhcp_max": 253, # }'; bundle agent lxc_host( cfg ) { vars: # var ip wird in mergedata verwendet, und mergedata mag keine flachen Variablen, # sondern braucht Arrays auf die eine oder andere Weise 'ip[thirdoctet]' string => format( "%02x", nth( splitstring( $(cfg[addr]), '\.', 4 ), 2 ) ); "cfg_ip" data => mergedata( cfg, ip ); debian|ubuntu:: 'lxc_path' string => '/etc/lxc'; 'lxc_net_path' string => '/etc/default/lxc-net'; 'lxc_net_tmpl' string => 'debian.lxc-net.mustache'; 'lxc_default_path' string => '$(lxc_path)/default.conf'; 'lxc_default_tmpl' string => 'default.conf.mustache'; 'lxc_hosts_d' string => '$(lxc_path)/hosts.d'; debian:: 'pkg_list' slist => { 'lxc','lxc-templates','debian-archive-keyring' }; ubuntu:: 'pkg_list' slist => { 'lxc','lxc-templates','ubuntu-archive-keyring' }; fedora|centos|redhat:: 'lxc_net_path' string => '/etc/lxc-net'; methods: 'Ensure the packages required for LXC are installed' usebundle => wmde_install_packages(@(pkg_list),'lxc'), comment => "The packages to install are defined in var `pkg_list`.", handle => "lxc_installed"; reports: "The agent $(default:def.agent_name) has been prepared as a host for lx containers."; "The host provides the network on $(cfg[bridge]) with address $(cfg[addr])."; "It will configure LXC via DHCP in the range $(cfg[dhcp_range])."; "Static mappings are in /etc/lxc/hosts.d."; "This is the third octet: $(ip[thirdoctet])."; files: "$(lxc_path)/." acl => lxc_dnsmasq, depends_on => { "lxc_installed" }; "$(lxc_path)/dnsmasq.conf" perms => mog('644','root','root'), content => "dhcp-hostsdir=$(lxc_hosts_d)", depends_on => { "lxc_installed" }; "$(lxc_hosts_d)/." create => "true", perms => mog('755','root','root'), handle => "lxchostsdir", depends_on => { "lxc_installed" }; "/usr/local/bin/lxc-hooks" perms => mog('700','root','root'), copy_from => local_cp("$(sys.workdir)/inputs/wmdelib/scripts/lxc-hooks.sh"), depends_on => { "lxc_installed" }, handle => "lxchookscript"; debian|ubuntu:: "$(lxc_net_path)" perms => mog('644','root', 'root'), template_data => @(cfg_ip), template_method => 'mustache', edit_template => "$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/$(lxc_net_tmpl)", depends_on => { "lxc_installed" }; "$(lxc_default_path)" perms => mog('644','root', 'root'), template_data => @(cfg_ip), template_method => 'mustache', edit_template => "$(sys.workdir)/inputs/$(def.wmde_libdir)/templates/lxc_host/$(lxc_default_tmpl)", depends_on => { "lxc_installed" }; } body acl lxc_dnsmasq { acl_method => "append"; acl_type => "posix"; aces => { "user:dnsmasq:rx:allow" }; } # example lxc.cfg # cfg data => ' # { # "name": "proxy", # "dist": "debian", # "release": "bookworm", # "arch": "amd64", # "bridge": "lxcbr0", # "ip": "10.0.11.80", # "group": "", # "autostart": true, # "policy": "absent|present", # "state": "stopped|running", # }'; # name # lxc.container.conf: # # Parameters passed to the template: --dist $(cfg[dist]) --arch $(cfg[arch]) --release $(cfg[release]) bundle agent lxc( cfg ) { classes: "cfg_array" expression => strcmp( type( "cfg", "true" ), "data array" ); vars: cfg_array:: "index" slist => getindices( @(cfg) ); methods: cfg_array:: "Iterate over config array: $(index)" usebundle => _lxc( @(cfg[$(index)]) ); !cfg_array:: "Forward config to" usebundle => _lxc( @(cfg) ); files: reports: cfg_array:: "Iterated over cfg array."; } # Although all attributes are passed in one data object there are # qualitative differences. All need the name element. # lxc-create needs dist, arch and release. # lxc config file needs autostart and group # dnsmasq needs the ip bundle agent _lxc( cfg ) { classes: "lxc_exists" expression => returnszero( "/usr/bin/lxc-info $(cfg[name]) 2> /dev/null", "noshell" ), scope => "bundle"; "lxc_host_file_exists" expression => fileexists( "$(lxc_host_file)" ), scope => "bundle"; "lxc_policy_exists" expression => isvariable( "cfg[policy]" ); "lxc_state_exists" expression => isvariable( "cfg[state]" ); lxc_policy_exists:: "lxc_policy_valid" expression => regcmp( "(absent|present)", "$(cfg[policy])" ); lxc_policy_valid:: 'present' expression => strcmp( "present", "$(cfg[policy])" ); !lxc_policy_exists:: 'present'; lxc_state_exists:: "lxc_state_valid" expression => regcmp( "(stopped|running)", "$(cfg[state])" ); lxc_state_valid:: 'running' expression => strcmp( "running", "$(cfg[state])" ); !lxc_state_exists:: 'running'; lxc_exists:: "lxc_correct_distribution" expression => regline( '^# Parameters passed to the template: --dist $(cfg[dist]) --arch $(cfg[arch]) --release $(cfg[release])$', "$(lxc_dir)/config" ), scope => "bundle"; vars: "lxc_host_file" string => "$(lxc_host.lxc_hosts_d)/$(cfg[name])"; "lxc_dir" string => "/var/lib/lxc/$(cfg[name])"; "lxc_rootfs" string => "$(lxc_dir)/rootfs"; 'autostart' string => $(cfg[autostart]); 'group' string => $(cfg[group]); files: !lxc_exists & present:: "/var/lib/lxc/$(cfg[name])/config" edit_line => lxc_config( "$(autostart)", "$(group)" ), depends_on => { "lxc_$(cfg[name])_created" }, handle => "$(cfg[name])_config_created"; methods: !lxc_exists & present:: "Ensure existence of container ($(cfg[name]))" usebundle => lxc_create( @(cfg) ), handle => "lxc_$(cfg[name])_created"; "Ensure static mapping in dnsmasq" usebundle => lxc_add_static_mapping( @(cfg) ), depends_on => { "lxc_$(cfg[name])_created" }, handle => "lxc_$(cfg[name])_mapped_statically"; lxc_exists & !present:: "Ensure absence of container ($cfg[name])" usebundle => lxc_destroy( @(cfg) ), handle => "lxc_$(cfg[name])_destroyed"; "Ensure absence of static mapping" usebundle => lxc_remove_static_mapping( @(cfg) ), depends_on => { "lxc_$(cfg[name])_destroyed" }, handle => "lxc_$(cfg[name])_unmapped_statically"; "Ensure dnsmasq picks up current lxc host configs" usebundle => reload_dnsmasq, depends_on => { "lxc_$(cfg[name])_unmapped_statically" }, handle => "reloaded_dnsmasq_for_$(cfg[name])"; present & running:: "Ensure running state of container ($(cfg[name]))" usebundle => lxc_start( @(cfg[name]) ), handle => "lxc_$(cfg[name])_started"; present & !running:: "Ensure stopped state of container ($(cfg[name]))" usebundle => lxc_stop( @(cfg[name]) ), handle => "lxc_$(cfg[name])_stopped"; reports: lxc_exists & lxc_correct_distribution:: "LX Container $(cfg[name]) already configured, nothing to do"; !lxc_exists & present:: "LXC $(cfg[name]) did not exist and should have been created."; lxc_exists & !present:: "LXC $(cfg[name]) did exist and should have been destroyed."; present & running:: "LXC $(cfg[name]) should now be in state RUNNING."; present & !running:: "LXC $(cfg[name]) should now be in state STOPPED."; } bundle edit_line lxc_config( autostart, group ) { classes: "autostart_true" expression => some( $(autostart_lc), true_statements ), depends_on => { "$(autostart)_lowercased" }; "group_provided" expression => isgreaterthan( $(group_length), 0 ); vars: "autostart_lc" string => string_downcase( $(autostart) ), handle => "$(autostart)_lowercased"; "group_length" int => string_length( $(group) ); "true_statements" slist => { "yes", "true", "on", "1" }, handle => "truth"; insert_lines: "# This file is managed by CFEngine. Manual changes will be overwritten." location => first_line; autostart_true:: "lxc.start.auto = 1"; !autostart_true:: "lxc.start.auto = 0"; group_provided:: "lxc.group = $(group)"; reports: "autostart ist $(autostart)"; "group ist $(group)"; } body location first_line { before_after => "before"; first_last => "first"; select_line_matching => ".*"; } bundle agent lxc_add_static_mapping( cfg ) { files: "$(lxc_host.lxc_hosts_d)/$(cfg[name])" perms => mog( '644', 'root', 'root' ), content => "$(cfg[name]),$(cfg[ip])", handle => "mapped_$(cfg[name])"; reports: "mapped $(cfg[name]) to $(cfg[ip])" depends_on => { "mapped_$(cfg[name])" }; } bundle agent lxc_remove_static_mapping( cfg ) { files: "$(lxc_host.lxc_hosts_d)/$(cfg[name])" delete => tidy, classes => if_repaired(dnsmasq_reload); reports: dnsmasq_reload:: "mapped $(cfg[name]) to $(cfg[ip])"; } # When files for static mappings are added dnsmasq automatically loads # them. But dnsmasq doesn't remove them automatically again when the # file gets removed. bundle agent reload_dnsmasq { processes: dnsmasq_reload:: "dnsmasq" signals => { "hup" }; } # DOWNLOAD_KEYSERVER="keyserver.ubuntu.com" lxc-create -n manual -t download -- -d debian -a amd64 -r bookworm bundle agent lxc_create( cfg ) { classes: "lxc_dir_btrfs" expression => strcmp( execresult( "/usr/bin/stat -f -c %T /var/lib/lxc" , "noshell", "stdout" ), "btrfs" ); vars: !lxc_dir_btrfs:: "create_args" slist => { "-n", $(cfg[name]), "-t", "download", "--", "-d", $(cfg[dist]), "-a", $(cfg[arch]), "-r", $(cfg[release]), }; lxc_dir_btrfs:: "create_args" slist => { "-n", $(cfg[name]), "-t", "download", "-B", "btrfs", "--", "-d", $(cfg[dist]), "-a", $(cfg[arch]), "-r", $(cfg[release]), }; commands: "/usr/bin/lxc-create" arglist => { @(create_args) }, contain => lxc_commands, handle => "lxc_$(cfg[name])_created"; reports: "LXC $(cfg[name]) has been created" depends_on => { "lxc_$(cfg[name])_created" }; } bundle agent lxc_destroy( cfg ) { methods: "Ensure LXC is stopped" usebundle => lxc_stop( $(cfg[name]) ), handle => "stopped_$(cfg[name])"; commands: "/usr/bin/lxc-destroy" arglist => { "-n", $(cfg[name]) }, depends_on => { "stopped_$(cfg[name])" }, handle => "destroyed_$(cfg[name])"; } bundle agent lxc_start( name ) { classes: "lxc_running" expression => strcmp( execresult( "/usr/bin/lxc-info -n $(name) -s -H", "noshell", "stdout" ), "RUNNING" ), scope => "bundle"; commands: !lxc_running:: "/usr/bin/lxc-start" arglist => { "-n", $(name) }, handle => "lxc_$(name)_started"; reports: !lxc_running:: "$(name) has been started" depends_on => { "lxc_$(name)_started" }; } bundle agent lxc_stop( name ) { classes: "lxc_running" expression => strcmp( execresult( "/usr/bin/lxc-info -n $(name) -s -H", "noshell", "stdout" ), "RUNNING" ), scope => "bundle"; commands: lxc_running:: "/usr/bin/lxc-stop" arglist => { "-n", $(name) }, handle => "lxc_$(name)_stopped"; reports: lxc_running:: "$(name) has been stopped" depends_on => { "lxc_$(name)_stopped" }; } body contain lxc_commands { useshell => "noshell"; no_output => "true"; }