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