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