]>
Commit | Line | Data |
---|---|---|
0c9a7596 AD |
1 | package PVE::QemuServer::Cloudinit; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use File::Path; | |
7 | use Digest::SHA; | |
8 | use URI::Escape; | |
9 | ||
10 | use PVE::Tools qw(run_command file_set_contents); | |
11 | use PVE::Storage; | |
12 | use PVE::QemuServer; | |
13 | ||
0c9a7596 AD |
14 | sub 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 | ||
24 | sub 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 | ||
57 | sub 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 | ||
96 | sub 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 | ||
111 | sub 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 | ||
158 | 1; |