adopt code for changes in pve-common
[pve-client.git] / PVE / APIClient / Config.pm
1 package PVE::APIClient::Config;
2
3 use strict;
4 use warnings;
5 use JSON;
6 use File::Basename qw(dirname);
7 use File::Path qw(make_path);
8
9 use PVE::APIClient::Helpers;
10 use PVE::APIClient::JSONSchema;
11 use PVE::APIClient::SectionConfig;
12 use PVE::APIClient::PTY;
13 use PVE::APIClient::Tools qw(file_get_contents file_set_contents);
14
15 use base qw(PVE::APIClient::SectionConfig);
16
17 my $remote_namne_regex = qw(\w+);
18
19 my $defaults_section = '!DEFAULTS';
20
21 my $complete_remote_name = sub {
22
23     my $config = PVE::APIClient::Config->load();
24     my $list = [];
25     foreach my $name (keys %{$config->{ids}}) {
26         push @$list, $name if $name ne $defaults_section;
27     }
28     return $list;
29 };
30
31 PVE::APIClient::JSONSchema::register_standard_option('pveclient-remote-name', {
32     description => "The name of the remote.",
33     type => 'string',
34     pattern => $remote_namne_regex,
35     completion => $complete_remote_name,
36 });
37
38 my $defaultData = {
39     propertyList => {
40         type => {
41             description => "Section type.",
42             optional => 1,
43         },
44     },
45 };
46
47 sub private {
48     return $defaultData;
49 }
50
51 sub config_filename {
52     my ($class) = @_;
53
54     my $dir = PVE::APIClient::Helpers::configuration_directory();
55
56     return "$dir/config";
57 }
58
59 sub lock_config {
60     my ($class, $timeout, $code, @param) = @_;
61
62     my $dir = PVE::APIClient::Helpers::configuration_directory();
63     make_path($dir);
64
65     my $filename = "$dir/.config.lck";
66
67     my $res = PVE::APIClient::Tools::lock_file($filename, $timeout, $code, @param);
68
69     die $@ if $@;
70
71     return $res;
72 }
73
74 sub format_section_header {
75     my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
76
77     if ($type eq 'defaults') {
78         return "defaults:\n";
79     } else {
80         return "$type: $sectionId\n";
81     }
82 }
83
84 sub parse_section_header {
85     my ($class, $line) = @_;
86
87     if ($line =~ m/^defaults:\s*$/) {
88         return ('defaults', $defaults_section, undef, {});
89     } elsif ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
90         my ($type, $name) = (lc($1), $2);
91         eval {
92             die "invalid remote name '$name'\n"
93                 if $name eq $defaults_section || $name !~ m/^$remote_namne_regex$/;
94         };
95         return ($type, $name, $@, {});
96     }
97     return undef;
98 }
99
100 sub load {
101     my ($class) = @_;
102
103     my $filename = $class->config_filename();
104
105     my $raw = '';
106
107     if (-e $filename) {
108         my $filemode = (stat($filename))[2] & 07777;
109         if ($filemode != 0600) {
110             die sprintf "wrong permissions on '$filename' %04o (expected 0600)\n", $filemode;
111         }
112
113         $raw = file_get_contents($filename);
114     }
115
116     return $class->parse_config($filename, $raw);
117 }
118
119 sub save {
120     my ($class, $cfg) = @_;
121
122     my $filename = $class->config_filename();
123
124     make_path(dirname($filename));
125
126     $cfg->{order}->{$defaults_section} = -1; # write as first section
127     my $raw = $class->write_config($filename, $cfg);
128
129     file_set_contents($filename, $raw, 0600);
130 }
131
132 sub get_defaults {
133     my ($class, $cfg) = @_;
134
135     $cfg->{ids}->{$defaults_section} //= {};
136
137     return $cfg->{ids}->{$defaults_section};
138 }
139
140 sub lookup_remote {
141     my ($class, $cfg, $name, $noerr) = @_;
142
143     my $data = $cfg->{ids}->{$name};
144
145     return $data if $noerr || defined($data);
146
147     die "unknown remote \"$name\"\n";
148 }
149
150 sub remote_conn {
151     my ($class, $cfg, $remote) = @_;
152
153     my $section = $class->lookup_remote($cfg, $remote);
154
155     my $trylogin = sub {
156         my ($ticket_or_password) = @_;
157
158         if (!defined($ticket_or_password)) {
159             $ticket_or_password = PVE::APIClient::PTY::read_password("Remote password: ")
160         }
161
162         my $setup = {
163             username                => $section->{username},
164             password                => $ticket_or_password,
165             host                    => $section->{host},
166             port                    => $section->{port} // 8006,
167             cached_fingerprints     => {
168                 $section->{fingerprint} => 1,
169             }
170         };
171
172         my $conn = PVE::APIClient::LWP->new(%$setup);
173
174         $conn->login();
175
176         return $conn;
177     };
178
179     my $password = $section->{password};
180
181     my $conn;
182
183     if (defined($password)) {
184         $conn = $trylogin->($password);
185     } else {
186
187         if (my $ticket = PVE::APIClient::Helpers::ticket_cache_lookup($remote)) {
188             eval { $conn = $trylogin->($ticket); };
189             if (my $err = $@) {
190                 PVE::APIClient::Helpers::ticket_cache_update($remote, undef);
191                 if (ref($err) && (ref($err) eq 'PVE::APIClient::Exception') && ($err->{code} == 401)) {
192                     $conn = $trylogin->();
193                 } else {
194                     die $err;
195                 }
196             }
197         } else {
198             $conn = $trylogin->();
199         }
200     }
201
202     PVE::APIClient::Helpers::ticket_cache_update($remote, $conn->{ticket});
203
204     return $conn;
205 }
206
207 package PVE::APIClient::RemoteConfig;
208
209 use strict;
210 use warnings;
211
212 use PVE::APIClient::JSONSchema qw(register_standard_option get_standard_option);
213 use PVE::APIClient::SectionConfig;
214
215 use base qw( PVE::APIClient::Config);
216
217 sub type {
218     return 'remote';
219 }
220
221 sub properties {
222     return {
223         name => get_standard_option('pveclient-remote-name'),
224         host => {
225             description => "The host.",
226             type => 'string', format => 'address',
227             optional => 1,
228         },
229         username => {
230             description => "The username.",
231             type => 'string',
232             optional => 1,
233         },
234         password => {
235             description => "The users password.",
236             type => 'string',
237             optional => 1,
238         },
239         port => {
240             description => "The port.",
241             type => 'integer',
242             optional => 1,
243             default => 8006,
244         },
245         fingerprint => {
246             description => "Fingerprint.",
247             type => 'string',
248             optional => 1,
249         },
250         comment => {
251             description => "Description.",
252             type => 'string',
253             optional => 1,
254             maxLength => 4096,
255         },
256     };
257 }
258
259 sub options {
260     return {
261         name => { optional => 0 },
262         host => { optional => 0 },
263         comment => { optional => 1 },
264         username => { optional => 0 },
265         password => { optional => 1 },
266         port => { optional => 1 },
267         fingerprint => { optional => 1 },
268    };
269 }
270
271 __PACKAGE__->register();
272
273
274 package PVE::APIClient::DefaultsConfig;
275
276 use strict;
277 use warnings;
278
279 use PVE::APIClient::JSONSchema qw(register_standard_option get_standard_option);
280
281 use base qw( PVE::APIClient::Config);
282
283
284 sub type {
285     return 'defaults';
286 }
287
288 sub options {
289     return {
290         name => { optional => 1 },
291         username => { optional => 1 },
292         port => { optional => 1 },
293    };
294 }
295
296 __PACKAGE__->register();
297
298
299 PVE::APIClient::Config->init();
300
301 1;