]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/Cloudinit.pm
cloud-init: clone/move support
[qemu-server.git] / PVE / QemuServer / Cloudinit.pm
CommitLineData
0c9a7596
AD
1package PVE::QemuServer::Cloudinit;
2
3use strict;
4use warnings;
5
6use File::Path;
7use Digest::SHA;
8use URI::Escape;
9
10use PVE::Tools qw(run_command file_set_contents);
11use PVE::Storage;
12use PVE::QemuServer;
13
0c9a7596
AD
14sub commit_cloudinit_disk {
15 my ($file_path, $iso_path, $format) = @_;
16
3db6e4ab 17 my $size = PVE::Storage::file_size_info($iso_path);
0c9a7596 18
3db6e4ab
WB
19 run_command([['genisoimage', '-R', '-V', 'config-2', $file_path],
20 ['qemu-img', 'dd', '-f', 'raw', '-O', $format,
21 'isize=0', "osize=$size", "of=$iso_path"]]);
0c9a7596
AD
22}
23
24sub generate_cloudinitconfig {
25 my ($conf, $vmid) = @_;
26
27 PVE::QemuServer::foreach_drive($conf, sub {
28 my ($ds, $drive) = @_;
29
30 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
31
32 return if !$volname || $volname !~ m/vm-$vmid-cloudinit/;
33
34 my $path = "/tmp/cloudinit/$vmid";
35
36 mkdir "/tmp/cloudinit";
37 mkdir $path;
38 mkdir "$path/drive";
39 mkdir "$path/drive/openstack";
40 mkdir "$path/drive/openstack/latest";
41 mkdir "$path/drive/openstack/content";
42 my $digest_data = generate_cloudinit_userdata($conf, $path)
43 . generate_cloudinit_network($conf, $path);
44 generate_cloudinit_metadata($conf, $path, $digest_data);
45
46 my $storecfg = PVE::Storage::config();
47 my $iso_path = PVE::Storage::path($storecfg, $drive->{file});
48 my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
49 my $format = PVE::QemuServer::qemu_img_format($scfg, $volname);
50 #fixme : add meta as drive property to compare
51 commit_cloudinit_disk("$path/drive", $iso_path, $format);
52 rmtree("$path/drive");
53 });
54}
55
56
57sub generate_cloudinit_userdata {
58 my ($conf, $path) = @_;
59
60 my $content = "#cloud-config\n";
61 my $hostname = $conf->{hostname};
62 if (!defined($hostname)) {
63 $hostname = $conf->{name};
64 if (my $search = $conf->{searchdomain}) {
65 $hostname .= ".$search";
66 }
67 }
68 $content .= "fqdn: $hostname\n";
69 $content .= "manage_etc_hosts: true\n";
70 $content .= "bootcmd: \n";
71 $content .= " - ifdown -a\n";
72 $content .= " - ifup -a\n";
73
74 my $keys = $conf->{sshkeys};
75 if ($keys) {
76 $keys = URI::Escape::uri_unescape($keys);
77 $keys = [map { chomp $_; $_ } split(/\n/, $keys)];
78 $keys = [grep { /\S/ } @$keys];
79
80 $content .= "users:\n";
81 $content .= " - default\n";
82 $content .= " - name: root\n";
83 $content .= " ssh-authorized-keys:\n";
84 foreach my $k (@$keys) {
85 $content .= " - $k\n";
86 }
87 }
88
89 $content .= "package_upgrade: true\n";
90
91 my $fn = "$path/drive/openstack/latest/user_data";
92 file_set_contents($fn, $content);
93 return $content;
94}
95
96sub generate_cloudinit_metadata {
97 my ($conf, $path, $digest_data) = @_;
98
99 my $uuid_str = Digest::SHA::sha1_hex($digest_data);
100
101 my $content = "{\n";
102 $content .= " \"uuid\": \"$uuid_str\",\n";
103 $content .= " \"network_config\" :{ \"content_path\": \"/content/0000\"}\n";
104 $content .= "}\n";
105
106 my $fn = "$path/drive/openstack/latest/meta_data.json";
107
108 file_set_contents($fn, $content);
109}
110
111sub generate_cloudinit_network {
112 my ($conf, $path) = @_;
113
114 my $content = "auto lo\n";
115 $content .="iface lo inet loopback\n\n";
116
117 my @ifaces = grep(/^net(\d+)$/, keys %$conf);
118 foreach my $iface (@ifaces) {
119 (my $id = $iface) =~ s/^net//;
120 next if !$conf->{"ipconfig$id"};
121 my $net = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"});
122 $id = "eth$id";
123
124 $content .="auto $id\n";
125 if ($net->{ip}) {
126 if ($net->{ip} eq 'dhcp') {
127 $content .= "iface $id inet dhcp\n";
128 } else {
129 my ($addr, $mask) = split('/', $net->{ip});
130 $content .= "iface $id inet static\n";
131 $content .= " address $addr\n";
132 $content .= " netmask $PVE::Network::ipv4_reverse_mask->[$mask]\n";
133 $content .= " gateway $net->{gw}\n" if $net->{gw};
134 }
135 }
136 if ($net->{ip6}) {
137 if ($net->{ip6} =~ /^(auto|dhcp)$/) {
138 $content .= "iface $id inet6 $1\n";
139 } else {
140 my ($addr, $mask) = split('/', $net->{ip6});
141 $content .= "iface $id inet6 static\n";
142 $content .= " address $addr\n";
143 $content .= " netmask $mask\n";
144 $content .= " gateway $net->{gw6}\n" if $net->{gw6};
145 }
146 }
147 }
148
149 $content .=" dns_nameservers $conf->{nameserver}\n" if $conf->{nameserver};
150 $content .=" dns_search $conf->{searchdomain}\n" if $conf->{searchdomain};
151
152 my $fn = "$path/drive/openstack/content/0000";
153 file_set_contents($fn, $content);
154 return $content;
155}
156
157
1581;