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